From bab8f563479a2f685ff1bfc9976326935ef29dee Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 9 Sep 2026 04:11:39 +0000
Subject: [PATCH 01/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.jules/palette.md | 6 ++---
apps/desktop/src/App.tsx | 49 ++++++++++++++++++++++++----------------
2 files changed, 32 insertions(+), 23 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index c05638899..0379b3823 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,3 @@
-## 2024-05-19 - Replace HTML disabled with aria-disabled="true" for Accessible Tooltips
-**Learning:** Native HTML `disabled` attributes completely hide elements from screen readers and block all pointer/hover events, preventing tooltips from functioning for disabled elements.
-**Action:** Replace `disabled` with `aria-disabled="true"`, enforce block click handlers via `e.preventDefault()`, and add a title tooltip directly to the element to maintain full tooltip accessibility and keyboard focus support for visually impaired and mouse users.
+## 2026-09-09 - Accessible Tooltips on Disabled Elements
+**Learning:** Native `title` attributes on `disabled` or `aria-disabled` elements do not reliably announce content to screen readers or display visually across all browsers in a consistent manner, especially for icon-only buttons.
+**Action:** When creating icon-only buttons that may be disabled, use `aria-disabled="true"` instead of the native `disabled` attribute so they can remain focusable, and wrap them in a custom, accessible `Tooltip` component from the design system to clearly explain the disabled state (e.g., "Coming soon").
diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx
index f3d678454..55a4748e1 100644
--- a/apps/desktop/src/App.tsx
+++ b/apps/desktop/src/App.tsx
@@ -48,6 +48,7 @@ import { ScoreView } from "./features/score/ScoreView";
import { Workspace } from "./features/workspace/Workspace";
import { EmptyState, ErrorState, LoadingState } from "./features/workspace/WorkspaceStates";
import { Button } from "@/components/ui/button";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Input } from "@/components/ui/input";
import { Progress } from "@/components/ui/progress";
import { Toaster } from "@/components/ui/sonner";
@@ -611,26 +612,34 @@ export function App() {
-
-
-
-
-
-
+
+
+
+
+
+ {t("settingsComingSoon")}
+
+
+
+
+
+
+
+ {t("helpComingSoon")}
+
+
From 885551e8d81ebe16856a549fcfb80441df8a52d3 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 9 Sep 2026 04:37:45 +0000
Subject: [PATCH 02/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
services/analysis-engine/tests/test_supply_chain_policy.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py
index 1d8224c5a..6a0853944 100644
--- a/services/analysis-engine/tests/test_supply_chain_policy.py
+++ b/services/analysis-engine/tests/test_supply_chain_policy.py
@@ -1275,9 +1275,7 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None:
workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8")
assert "concurrency:" in workflow, workflow_name
assert "cancel-in-progress: false" in workflow, workflow_name
- assert "contents: read" in workflow or "permissions: read-all" in workflow, (
- workflow_name
- )
+ assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name
assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8")
From 26055edcadacde997191cb1ad2c558d950f48b6c Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 9 Sep 2026 05:18:44 +0000
Subject: [PATCH 03/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 2d678ee2aecf3a3caf7faf8fc6a92d8074f8ab9f Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 9 Sep 2026 05:41:35 +0000
Subject: [PATCH 04/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From cb92397039b48c6893b12e521584e61da89075e3 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 9 Sep 2026 05:51:45 +0000
Subject: [PATCH 05/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 1ce663816be4fc2e84b5d444c052b9afea126ee1 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 9 Sep 2026 20:07:39 +0900
Subject: [PATCH 06/45] test(ui): pin tooltip locale and reduced-motion
resilience
---
.../components/ui/tooltip.resilience.test.tsx | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 apps/desktop/src/components/ui/tooltip.resilience.test.tsx
diff --git a/apps/desktop/src/components/ui/tooltip.resilience.test.tsx b/apps/desktop/src/components/ui/tooltip.resilience.test.tsx
new file mode 100644
index 000000000..9ea9a68b8
--- /dev/null
+++ b/apps/desktop/src/components/ui/tooltip.resilience.test.tsx
@@ -0,0 +1,23 @@
+import { render } from "@testing-library/react"
+import { describe, expect, it } from "vitest"
+
+import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"
+
+describe("Tooltip resilience contract", () => {
+ it("bounds expanded locale copy and disables decorative motion when requested", () => {
+ render(
+
+ Settings
+
+ Einstellungen sind in dieser Version noch nicht verfügbar und werden später bereitgestellt.
+
+
+ )
+
+ const tooltipContent = document.querySelector('[data-slot="tooltip-content"]')
+ expect(tooltipContent).toBeTruthy()
+ expect(tooltipContent).toHaveClass("max-w-[min(20rem,calc(100vw-2rem))]")
+ expect(tooltipContent).toHaveClass("break-words")
+ expect(tooltipContent).toHaveClass("motion-reduce:transition-none")
+ })
+})
From 8cb4eea4359589cf63d15463973edd1521baed58 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 9 Sep 2026 20:07:59 +0900
Subject: [PATCH 07/45] fix(ui): bound tooltip expansion and reduced motion
---
apps/desktop/src/components/ui/tooltip.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/desktop/src/components/ui/tooltip.tsx b/apps/desktop/src/components/ui/tooltip.tsx
index 8b954764b..790da93b1 100644
--- a/apps/desktop/src/components/ui/tooltip.tsx
+++ b/apps/desktop/src/components/ui/tooltip.tsx
@@ -36,8 +36,8 @@ function TooltipContent({
Date: Wed, 9 Sep 2026 20:08:29 +0900
Subject: [PATCH 08/45] docs(ui): record disabled tooltip interaction boundary
---
.jules/palette.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index 0379b3823..092cd02ef 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,3 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
-**Learning:** Native `title` attributes on `disabled` or `aria-disabled` elements do not reliably announce content to screen readers or display visually across all browsers in a consistent manner, especially for icon-only buttons.
-**Action:** When creating icon-only buttons that may be disabled, use `aria-disabled="true"` instead of the native `disabled` attribute so they can remain focusable, and wrap them in a custom, accessible `Tooltip` component from the design system to clearly explain the disabled state (e.g., "Coming soon").
+**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
+**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
From e31f32e01afdac4db02fb152e364d7dafd760fb1 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 9 Sep 2026 20:11:04 +0900
Subject: [PATCH 09/45] docs(ui): doctor disabled tooltip accessibility
boundary
---
docs/doctoring/sidebar-disabled-tooltips.md | 52 +++++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 docs/doctoring/sidebar-disabled-tooltips.md
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
new file mode 100644
index 000000000..8b32a10cf
--- /dev/null
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -0,0 +1,52 @@
+# Sidebar disabled-tooltip accessibility boundary
+
+## Problem and buyer impact
+
+The Settings and Help controls are intentionally unavailable but remain discoverable in the sidebar. Native `title` text is not the product authority for the unavailable state: the icon-only triggers need an accessible name independent of tooltip rendering, activation must remain suppressed while `aria-disabled="true"` keeps the controls in the focus order, and tooltip copy must remain readable when translated text expands or the viewport narrows.
+
+The shared Tooltip primitive also used an opacity transition for appearance/disappearance without an explicit reduced-motion override. The effect is small, but it is decorative rather than necessary to convey state, so the primitive should respect the operating-system/browser reduced-motion preference.
+
+## Constraints
+
+- Keep Settings and Help unavailable; this slice does not implement either feature.
+- Keep the controls keyboard-discoverable and expose the same localized unavailable-state text as the accessible name and tooltip explanation.
+- `aria-disabled` is semantic state, not an interaction lock. Existing `preventUnavailableAction` remains responsible for suppressing pointer- and keyboard-generated activation.
+- Do not create a second locale ledger or copy translation authority into the Tooltip primitive.
+- Long translated text must wrap within the viewport instead of depending on a fixed English-sized popup.
+- The primitive must not claim browser, Narrator, VoiceOver, touch, or visual acceptance from jsdom/class assertions alone.
+
+## Decision
+
+The sidebar uses the existing design-system `Tooltip`, with `TooltipTrigger` retaining `aria-disabled="true"`, a localized `aria-label`, and `preventUnavailableAction`. The shared popup gains a viewport-bounded maximum width, word breaking for long tokens, and `motion-reduce:transition-none` for its decorative opacity transition.
+
+A focused regression renders expanded German tooltip copy and pins the responsive/reduced-motion class contract. This is deliberately a component contract rather than a product-level accessibility acceptance test.
+
+Alternatives rejected:
+
+- Native `disabled`: removes these currently unavailable controls from ordinary keyboard focus and defeats the chosen discoverability contract.
+- `aria-disabled` without an event guard: exposes state but leaves activation behavior enabled.
+- Tooltip-only naming: makes the accessible name depend on popup behavior rather than the trigger itself.
+- Fixed popup width sized for English/Korean: does not address CJK/European-language expansion or narrow viewports.
+- Removing all Tooltip animation globally: unnecessary; honoring the user preference is the narrower control.
+
+## Evidence and claim boundary
+
+MDN states that `aria-disabled="true"` communicates disabled semantics but does not suppress functionality; developers must suppress behavior themselves. W3C WCAG 2.2 SC 1.4.10 explains that ordinary text content should reflow within a viewport rather than force two-dimensional scrolling. MDN defines `prefers-reduced-motion` as the user preference for reducing non-essential motion. These sources support the chosen interaction, wrapping, and motion-preference boundaries; they do not prove BandScope conformance by themselves.
+
+Current automated evidence covers DOM semantics already present in `App.test.tsx` plus the Tooltip class contract. Current-head browser geometry, actual hover/focus popup placement, forced-colors behavior, Narrator/VoiceOver announcements, pointer/touch behavior, 400% zoom, and KO/EN/JA/ZH/VI/ES/DE/FR rendered acceptance remain separate UI Delivery Gate evidence.
+
+## TRACEABILITY
+
+- Sidebar product integration: `apps/desktop/src/App.tsx`
+- Shared primitive: `apps/desktop/src/components/ui/tooltip.tsx`
+- Existing focusable unavailable-control contract: `apps/desktop/src/App.test.tsx`
+- Expanded-copy/reduced-motion regression: `apps/desktop/src/components/ui/tooltip.resilience.test.tsx`
+- Reviewer learning note: `.jules/palette.md`
+
+## References
+
+Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
+
+Mozilla Developer Network. (2026, June 10). *prefers-reduced-motion CSS media feature*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
+
+W3C Accessibility Guidelines Working Group. (2026, August 10). *Understanding Success Criterion 1.4.10: Reflow*. W3C Web Accessibility Initiative. https://www.w3.org/WAI/WCAG22/Understanding/reflow
From c3ae33681974dcb3bfeabfd7140fb2a9a4702113 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 9 Sep 2026 20:13:53 +0900
Subject: [PATCH 10/45] test(ui): verify Base UI tooltip trigger forwarding
---
.../components/ui/tooltip.resilience.test.tsx | 32 +++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/apps/desktop/src/components/ui/tooltip.resilience.test.tsx b/apps/desktop/src/components/ui/tooltip.resilience.test.tsx
index 9ea9a68b8..df187580e 100644
--- a/apps/desktop/src/components/ui/tooltip.resilience.test.tsx
+++ b/apps/desktop/src/components/ui/tooltip.resilience.test.tsx
@@ -1,9 +1,37 @@
-import { render } from "@testing-library/react"
-import { describe, expect, it } from "vitest"
+import { fireEvent, render, screen } from "@testing-library/react"
+import { describe, expect, it, vi } from "vitest"
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"
describe("Tooltip resilience contract", () => {
+ it("keeps Base UI trigger button semantics and native props", () => {
+ const onClick = vi.fn()
+
+ render(
+
+
+ Settings
+
+ Settings coming soon
+
+ )
+
+ const trigger = screen.getByRole("button", { name: "Settings coming soon" })
+ expect(trigger.tagName).toBe("BUTTON")
+ expect(trigger).toHaveAttribute("type", "button")
+ expect(trigger).toHaveAttribute("aria-disabled", "true")
+ expect(trigger).toHaveClass("focus-contract")
+
+ fireEvent.click(trigger)
+ expect(onClick).toHaveBeenCalledTimes(1)
+ })
+
it("bounds expanded locale copy and disables decorative motion when requested", () => {
render(
From fc5ba90985767e6c455ed7af83742b6dd0b1ee05 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 9 Sep 2026 20:14:32 +0900
Subject: [PATCH 11/45] docs(ui): trace Base UI trigger forwarding evidence
---
docs/doctoring/sidebar-disabled-tooltips.md | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 8b32a10cf..328dde49e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -19,13 +19,16 @@ The shared Tooltip primitive also used an opacity transition for appearance/disa
The sidebar uses the existing design-system `Tooltip`, with `TooltipTrigger` retaining `aria-disabled="true"`, a localized `aria-label`, and `preventUnavailableAction`. The shared popup gains a viewport-bounded maximum width, word breaking for long tokens, and `motion-reduce:transition-none` for its decorative opacity transition.
-A focused regression renders expanded German tooltip copy and pins the responsive/reduced-motion class contract. This is deliberately a component contract rather than a product-level accessibility acceptance test.
+A focused regression renders expanded German tooltip copy and pins the responsive/reduced-motion class contract. The same regression verifies the exact component-library integration: BandScope locks `@base-ui/react` 1.7.0, whose `TooltipTrigger` explicitly renders a `` by default and calls `useRenderElement('button', componentProps, ...)` with external element props in the merged prop list. The test therefore asserts the native `BUTTON` tag plus `type`, `aria-disabled`, class, and click-handler forwarding instead of applying Radix-specific `asChild` assumptions to Base UI.
+
+This is deliberately a component contract rather than a product-level accessibility acceptance test.
Alternatives rejected:
- Native `disabled`: removes these currently unavailable controls from ordinary keyboard focus and defeats the chosen discoverability contract.
- `aria-disabled` without an event guard: exposes state but leaves activation behavior enabled.
- Tooltip-only naming: makes the accessible name depend on popup behavior rather than the trigger itself.
+- Radix `asChild` repair: BandScope does not use Radix Tooltip here. The locked Base UI 1.7.0 trigger already renders the native button and forwards the supplied props.
- Fixed popup width sized for English/Korean: does not address CJK/European-language expansion or narrow viewports.
- Removing all Tooltip animation globally: unnecessary; honoring the user preference is the narrower control.
@@ -33,15 +36,19 @@ Alternatives rejected:
MDN states that `aria-disabled="true"` communicates disabled semantics but does not suppress functionality; developers must suppress behavior themselves. W3C WCAG 2.2 SC 1.4.10 explains that ordinary text content should reflow within a viewport rather than force two-dimensional scrolling. MDN defines `prefers-reduced-motion` as the user preference for reducing non-essential motion. These sources support the chosen interaction, wrapping, and motion-preference boundaries; they do not prove BandScope conformance by themselves.
-Current automated evidence covers DOM semantics already present in `App.test.tsx` plus the Tooltip class contract. Current-head browser geometry, actual hover/focus popup placement, forced-colors behavior, Narrator/VoiceOver announcements, pointer/touch behavior, 400% zoom, and KO/EN/JA/ZH/VI/ES/DE/FR rendered acceptance remain separate UI Delivery Gate evidence.
+The repository lockfile identifies `@base-ui/react` 1.7.0. Upstream tag `v1.7.0` documents `TooltipTrigger` as rendering a `` and implements it with `useRenderElement('button', componentProps, ...)`, including `elementProps` in the merged props. A stale review that reasoned from Radix semantics was therefore dismissed as factually inapplicable after adding a repository regression for native-button/prop forwarding. Dismissing that stale finding is not an approval and does not satisfy the current-head review gate.
+
+Current automated evidence covers DOM semantics already present in `App.test.tsx`, Base UI trigger forwarding, and the Tooltip class contract. Current-head browser geometry, actual hover/focus popup placement, forced-colors behavior, Narrator/VoiceOver announcements, pointer/touch behavior, 400% zoom, and KO/EN/JA/ZH/VI/ES/DE/FR rendered acceptance remain separate UI Delivery Gate evidence.
## TRACEABILITY
- Sidebar product integration: `apps/desktop/src/App.tsx`
- Shared primitive: `apps/desktop/src/components/ui/tooltip.tsx`
- Existing focusable unavailable-control contract: `apps/desktop/src/App.test.tsx`
-- Expanded-copy/reduced-motion regression: `apps/desktop/src/components/ui/tooltip.resilience.test.tsx`
+- Trigger-forwarding and expanded-copy/reduced-motion regression: `apps/desktop/src/components/ui/tooltip.resilience.test.tsx`
- Reviewer learning note: `.jules/palette.md`
+- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
+- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
## References
From 99d4768527d408e791165cfd797ec8760631aa3f Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 9 Sep 2026 20:16:55 +0900
Subject: [PATCH 12/45] repair(ui): return formatter prerequisite to canonical
owner
---
services/analysis-engine/tests/test_supply_chain_policy.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py
index 6a0853944..1d8224c5a 100644
--- a/services/analysis-engine/tests/test_supply_chain_policy.py
+++ b/services/analysis-engine/tests/test_supply_chain_policy.py
@@ -1275,7 +1275,9 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None:
workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8")
assert "concurrency:" in workflow, workflow_name
assert "cancel-in-progress: false" in workflow, workflow_name
- assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name
+ assert "contents: read" in workflow or "permissions: read-all" in workflow, (
+ workflow_name
+ )
assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8")
From 0d877be10fe95903535f64648db37410f968dabe Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 9 Sep 2026 11:30:22 +0000
Subject: [PATCH 13/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
services/analysis-engine/tests/test_supply_chain_policy.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py
index 1d8224c5a..6a0853944 100644
--- a/services/analysis-engine/tests/test_supply_chain_policy.py
+++ b/services/analysis-engine/tests/test_supply_chain_policy.py
@@ -1275,9 +1275,7 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None:
workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8")
assert "concurrency:" in workflow, workflow_name
assert "cancel-in-progress: false" in workflow, workflow_name
- assert "contents: read" in workflow or "permissions: read-all" in workflow, (
- workflow_name
- )
+ assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name
assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8")
From 7b4378ffbac7d827e3c6a8041bcadddee187b6ec Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Thu, 10 Sep 2026 19:10:03 +0900
Subject: [PATCH 14/45] chore(ui): restore tooltip lane single-writer boundary
---
services/analysis-engine/tests/test_supply_chain_policy.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py
index 6a0853944..1d8224c5a 100644
--- a/services/analysis-engine/tests/test_supply_chain_policy.py
+++ b/services/analysis-engine/tests/test_supply_chain_policy.py
@@ -1275,7 +1275,9 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None:
workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8")
assert "concurrency:" in workflow, workflow_name
assert "cancel-in-progress: false" in workflow, workflow_name
- assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name
+ assert "contents: read" in workflow or "permissions: read-all" in workflow, (
+ workflow_name
+ )
assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8")
From 8ab0bb9deb632288020e108dd8bbf2ccd1f078c7 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 10 Sep 2026 10:24:38 +0000
Subject: [PATCH 15/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
services/analysis-engine/tests/test_supply_chain_policy.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py
index 1d8224c5a..6a0853944 100644
--- a/services/analysis-engine/tests/test_supply_chain_policy.py
+++ b/services/analysis-engine/tests/test_supply_chain_policy.py
@@ -1275,9 +1275,7 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None:
workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8")
assert "concurrency:" in workflow, workflow_name
assert "cancel-in-progress: false" in workflow, workflow_name
- assert "contents: read" in workflow or "permissions: read-all" in workflow, (
- workflow_name
- )
+ assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name
assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8")
From 3ebcacca5235efaa2d715118fd50c41eadfecdcc Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 10 Sep 2026 10:44:23 +0000
Subject: [PATCH 16/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From c73246ff15d7f8c740ab6cf42dc9224270d61d79 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 10 Sep 2026 21:16:28 +0000
Subject: [PATCH 17/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 0bb7c31667916d93bcad092227e1c298f900d820 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 06:21:13 +0900
Subject: [PATCH 18/45] docs(ui): record tooltip reference security boundary
---
docs/doctoring/sidebar-disabled-tooltips.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 328dde49e..4d49e175e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,10 +50,14 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+## Security Notes
+
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
Mozilla Developer Network. (2026, June 10). *prefers-reduced-motion CSS media feature*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
-W3C Accessibility Guidelines Working Group. (2026, August 10). *Understanding Success Criterion 1.4.10: Reflow*. W3C Web Accessibility Initiative. https://www.w3.org/WAI/WCAG22/Understanding/reflow
+W3C Accessibility Guidelines Working Group. (2026, August 10). *Understanding Success Criterion 1.4.10: Reflow*. W3C Web Accessibility Initiative. https://www.w3.org/WAI/WCAG22/Understanding/reflow
\ No newline at end of file
From 4e7f0ef568b2fddf0091f3f7bf6c00296353a70f Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 10 Sep 2026 21:25:41 +0000
Subject: [PATCH 19/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/doctoring/sidebar-disabled-tooltips.md | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 4d49e175e..328dde49e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,14 +50,10 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
-## Security Notes
-
-The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
-
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
Mozilla Developer Network. (2026, June 10). *prefers-reduced-motion CSS media feature*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
-W3C Accessibility Guidelines Working Group. (2026, August 10). *Understanding Success Criterion 1.4.10: Reflow*. W3C Web Accessibility Initiative. https://www.w3.org/WAI/WCAG22/Understanding/reflow
\ No newline at end of file
+W3C Accessibility Guidelines Working Group. (2026, August 10). *Understanding Success Criterion 1.4.10: Reflow*. W3C Web Accessibility Initiative. https://www.w3.org/WAI/WCAG22/Understanding/reflow
From 95eedd1318a78285611cc944e4efc00d51bac4b8 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 10:12:47 +0900
Subject: [PATCH 20/45] docs(ui): restore tooltip security boundary
---
docs/doctoring/sidebar-disabled-tooltips.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 328dde49e..683eeefa4 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,6 +50,10 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+## Security Notes
+
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From 1692ef2a307cb02c94d0290ce31d4bf751e562ac Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 01:19:59 +0000
Subject: [PATCH 21/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/doctoring/sidebar-disabled-tooltips.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 683eeefa4..328dde49e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,10 +50,6 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
-## Security Notes
-
-The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
-
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From ed8c5deb4fdc8c9d4766ca0e65e4032ad85e4e82 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 11:10:31 +0900
Subject: [PATCH 22/45] docs(ui): restore tooltip security boundary
---
docs/doctoring/sidebar-disabled-tooltips.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 328dde49e..683eeefa4 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,6 +50,10 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+## Security Notes
+
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From ee94034aa771d5a3059fcccd40c09018c2fb08f7 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 11:13:04 +0900
Subject: [PATCH 23/45] docs(ui): pin tooltip security-note preservation
---
.jules/palette.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.jules/palette.md b/.jules/palette.md
index 092cd02ef..91300fb20 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,7 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
+
+## 2026-09-11 - Preserve doctoring security boundaries during regeneration
+**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
+**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
From 1018c0adf7f8d37f87e5c84a4c98c007e9153e91 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 02:18:43 +0000
Subject: [PATCH 24/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.jules/palette.md | 4 ----
docs/doctoring/sidebar-disabled-tooltips.md | 4 ----
2 files changed, 8 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index 91300fb20..092cd02ef 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,7 +1,3 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
-
-## 2026-09-11 - Preserve doctoring security boundaries during regeneration
-**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
-**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 683eeefa4..328dde49e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,10 +50,6 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
-## Security Notes
-
-The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
-
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From 83dd4f9356885f42fad5cfb8357b5ef7da4d7034 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 12:12:11 +0900
Subject: [PATCH 25/45] docs(tooltip): restore reviewed security boundary
---
docs/doctoring/sidebar-disabled-tooltips.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 328dde49e..683eeefa4 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,6 +50,10 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+## Security Notes
+
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From 04968592fa0d31d636c5e5c24c7015be11410a3c Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 12:12:26 +0900
Subject: [PATCH 26/45] docs(tooltip): restore regeneration guardrail
---
.jules/palette.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.jules/palette.md b/.jules/palette.md
index 092cd02ef..91300fb20 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,7 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
+
+## 2026-09-11 - Preserve doctoring security boundaries during regeneration
+**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
+**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
From e329c9f673cf20487b291e334b1a3c41d4314611 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 03:17:04 +0000
Subject: [PATCH 27/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.jules/palette.md | 4 ----
docs/doctoring/sidebar-disabled-tooltips.md | 4 ----
2 files changed, 8 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index 91300fb20..092cd02ef 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,7 +1,3 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
-
-## 2026-09-11 - Preserve doctoring security boundaries during regeneration
-**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
-**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 683eeefa4..328dde49e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,10 +50,6 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
-## Security Notes
-
-The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
-
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From 37eb72df7ab3808fad350c0cb7249a90f9042345 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 14:13:09 +0900
Subject: [PATCH 28/45] docs(security): restore tooltip trust boundary
---
docs/doctoring/sidebar-disabled-tooltips.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 328dde49e..683eeefa4 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,6 +50,10 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+## Security Notes
+
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From fe530c2339d4a2601c6dab3120ea10fc07e8cbb4 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 14:13:15 +0900
Subject: [PATCH 29/45] docs(palette): preserve tooltip security boundary
---
.jules/palette.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.jules/palette.md b/.jules/palette.md
index 092cd02ef..91300fb20 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,7 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
+
+## 2026-09-11 - Preserve doctoring security boundaries during regeneration
+**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
+**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
From 65bd7f51e329d2241a9ce30c0e44bcf7e35e3cee Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 05:18:22 +0000
Subject: [PATCH 30/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.jules/palette.md | 4 ----
docs/doctoring/sidebar-disabled-tooltips.md | 4 ----
2 files changed, 8 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index 91300fb20..092cd02ef 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,7 +1,3 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
-
-## 2026-09-11 - Preserve doctoring security boundaries during regeneration
-**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
-**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 683eeefa4..328dde49e 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,10 +50,6 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
-## Security Notes
-
-The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
-
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From 5e240de99a20c8ceea0e967a085b20df19f0664f Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 15:12:32 +0900
Subject: [PATCH 31/45] docs(security): restore Tooltip trust-boundary evidence
---
docs/doctoring/sidebar-disabled-tooltips.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 328dde49e..683eeefa4 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -50,6 +50,10 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+## Security Notes
+
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+
## References
Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-disabled
From c9e5f2604e768b8f4deacdbca4cd7fab58ce2e60 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 15:12:43 +0900
Subject: [PATCH 32/45] docs(governance): preserve Tooltip security
regeneration note
---
.jules/palette.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.jules/palette.md b/.jules/palette.md
index 092cd02ef..91300fb20 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,7 @@
## 2026-09-09 - Accessible Tooltips on Disabled Elements
**Learning:** `aria-disabled="true"` exposes unavailable state but does not suppress activation. Icon-only controls therefore need an accessible name independent of the tooltip, and unavailable activation must still be blocked in code.
**Action:** Keep unavailable icon buttons focusable with `aria-disabled="true"`, give the trigger an `aria-label` matching the Tooltip text, and route pointer or keyboard-generated clicks through `preventUnavailableAction`. Use the design-system Tooltip only to explain the unavailable state; do not log or expose user-controlled values through tooltip copy.
+
+## 2026-09-11 - Preserve doctoring security boundaries during regeneration
+**Learning:** This Tooltip slice cites external MDN, W3C, and Base UI documentation. Two later branch regenerations removed the reviewed `Security Notes` paragraph even though the runtime trust/network boundary had not changed, repeatedly reopening the same governance finding.
+**Action:** When updating `docs/doctoring/sidebar-disabled-tooltips.md`, preserve its `Security Notes` section whenever external references remain. The note must keep those URLs documentation-only and state that this Tooltip slice adds no runtime fetch/navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. If that security boundary truly changes, update the note and tests/review evidence explicitly rather than deleting it as formatting or generated-copy cleanup.
From 096db0df276ebd93d7e05b93d121405290c36b85 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 06:16:24 +0000
Subject: [PATCH 33/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From a4a0200b4cf4f8aec7b0774500df102f6b682829 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 11 Sep 2026 15:17:11 +0900
Subject: [PATCH 34/45] docs(security): structure Tooltip trust-boundary
statement
---
docs/doctoring/sidebar-disabled-tooltips.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 683eeefa4..16b9236c1 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -52,6 +52,8 @@ Current automated evidence covers DOM semantics already present in `App.test.tsx
## Security Notes
+### Trust boundary
+
The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
## References
From 105ee5b8066689acbc5a6ccb123602a4cc2588be Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 06:21:44 +0000
Subject: [PATCH 35/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 125788943cf51a23ec845b2e086617072cd7ca12 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 11 Sep 2026 06:29:54 +0000
Subject: [PATCH 36/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 3d92e713ac98f167e76db3b8e4fc20f93eee1f62 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sat, 19 Sep 2026 14:08:20 +0000
Subject: [PATCH 37/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?=
=?UTF-8?q?=EA=B7=BC=EC=84=B1=20=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84?=
=?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
네이티브 title 속성을 사용하는 버튼에 Tooltip 컴포넌트를 적용했습니다. 네이티브 title 속성은 aria-disabled 요소에 대해 스크린 리더 및 키보드 사용자의 접근성을 제대로 지원하지 못하기 때문에, 접근성을 개선하기 위해 변경했습니다.
---
apps/desktop/src/App.test.tsx | 4 +-
apps/desktop/src/App.tsx | 154 ++++++++++++++++++++++------------
2 files changed, 101 insertions(+), 57 deletions(-)
diff --git a/apps/desktop/src/App.test.tsx b/apps/desktop/src/App.test.tsx
index 3eed386f8..58d217e7e 100644
--- a/apps/desktop/src/App.test.tsx
+++ b/apps/desktop/src/App.test.tsx
@@ -225,7 +225,7 @@ describe("App", () => {
for (const name of ["Import", "Export"]) {
const navButton = within(primaryNav).getByRole("button", { name });
expect(navButton).toHaveAttribute("aria-disabled", "true");
- expect(navButton).toHaveAttribute("title", "Coming soon");
+ expect(navButton).not.toHaveAttribute("title");
expect(navButton).not.toBeDisabled();
}
fireEvent.click(within(primaryNav).getByRole("button", { name: "Import" }));
@@ -236,7 +236,7 @@ describe("App", () => {
for (const name of ["Import", "Export"]) {
const navButton = within(compactNav).getByRole("button", { name: `${name} compact view` });
expect(navButton).toHaveAttribute("aria-disabled", "true");
- expect(navButton).toHaveAttribute("title", "Coming soon");
+ expect(navButton).not.toHaveAttribute("title");
expect(navButton).not.toBeDisabled();
}
fireEvent.click(within(compactNav).getByRole("button", { name: "Import compact view" }));
diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx
index f3d678454..110b3aa77 100644
--- a/apps/desktop/src/App.tsx
+++ b/apps/desktop/src/App.tsx
@@ -24,6 +24,7 @@ import {
X,
type LucideIcon,
} from "lucide-react";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import {
SUPPORTED_AUDIO_FORMATS,
type AnalysisJobRequest,
@@ -565,25 +566,43 @@ export function App() {
const { label, enabled, active, title } = navButtonState(item);
const { icon: Icon, view } = item;
- return (
+ const buttonClass = `flex min-h-11 w-full items-center gap-3 rounded-xl px-3 text-left text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${
+ active
+ ? "bg-blue-600/70 text-white shadow-[0_12px_30px_rgba(37,99,235,0.32)]"
+ : enabled
+ ? "text-slate-200 hover:bg-white/5"
+ : "cursor-not-allowed text-slate-500 opacity-70"
+ }`;
+
+ const buttonContent = (
+ <>
+
+ {label}
+ >
+ );
+
+ return enabled ? (
handleNavSelect(view) : blockInactiveNavActivation}
- className={`flex min-h-11 w-full items-center gap-3 rounded-xl px-3 text-left text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${
- active
- ? "bg-blue-600/70 text-white shadow-[0_12px_30px_rgba(37,99,235,0.32)]"
- : enabled
- ? "text-slate-200 hover:bg-white/5"
- : "cursor-not-allowed text-slate-500 opacity-70"
- }`}
+ onClick={view ? () => handleNavSelect(view) : blockInactiveNavActivation}
+ className={buttonClass}
>
-
- {label}
+ {buttonContent}
+ ) : (
+
+
+ {buttonContent}
+
+ {title && {title} }
+
);
})}
@@ -611,26 +630,30 @@ export function App() {
-
-
-
-
-
-
+
+
+
+
+ {t("settingsComingSoon")}
+
+
+
+
+
+ {t("helpComingSoon")}
+
@@ -641,26 +664,45 @@ export function App() {
const { label, enabled, active, title } = navButtonState(item);
const { icon: Icon, view } = item;
- return (
+ const buttonClass = `inline-flex min-h-10 shrink-0 items-center gap-2 rounded-xl px-3 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${
+ active
+ ? "bg-blue-600/70 text-white"
+ : enabled
+ ? "text-slate-200 hover:bg-white/5"
+ : "cursor-not-allowed text-slate-500 opacity-70"
+ }`;
+
+ const buttonContent = (
+ <>
+
+ {label}
+ >
+ );
+
+ return enabled ? (
handleNavSelect(view) : blockInactiveNavActivation}
- className={`inline-flex min-h-10 shrink-0 items-center gap-2 rounded-xl px-3 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${
- active
- ? "bg-blue-600/70 text-white"
- : enabled
- ? "text-slate-200 hover:bg-white/5"
- : "cursor-not-allowed text-slate-500 opacity-70"
- }`}
+ onClick={view ? () => handleNavSelect(view) : blockInactiveNavActivation}
+ className={buttonClass}
>
-
- {label}
+ {buttonContent}
+ ) : (
+
+
+ {buttonContent}
+
+ {title && {title} }
+
);
})}
@@ -709,15 +751,17 @@ export function App() {
aria-describedby={selectionError && selectionErrorSource === "youtube" ? "selection-error" : undefined}
/>
{youtubeUrl && !analysisInFlight && !isStarting && !isImporting ? (
-
-
-
+
+
+
+
+ {t("clearYoutubeUrl")}
+
) : null}
From a1b68555bd658bcaa6c89c78e378082fe300ad6b Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sun, 20 Sep 2026 00:31:30 +0000
Subject: [PATCH 38/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?=
=?UTF-8?q?=EA=B7=BC=EC=84=B1=20=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84?=
=?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
네이티브 title 속성을 사용하는 버튼에 Tooltip 컴포넌트를 적용했습니다. 네이티브 title 속성은 aria-disabled 요소에 대해 스크린 리더 및 키보드 사용자의 접근성을 제대로 지원하지 못하기 때문에, 접근성을 개선하기 위해 변경했습니다.
From 353fb66cc447a04760285996af79526acb55cf03 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sun, 20 Sep 2026 18:32:56 +0000
Subject: [PATCH 39/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?=
=?UTF-8?q?=EA=B7=BC=EC=84=B1=20=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84?=
=?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
네이티브 title 속성을 사용하는 버튼에 Tooltip 컴포넌트를 적용했습니다. 네이티브 title 속성은 aria-disabled 요소에 대해 스크린 리더 및 키보드 사용자의 접근성을 제대로 지원하지 못하기 때문에, 접근성을 개선하기 위해 변경했습니다.
From c64959c6fa9f98ee589e7fdc84021c6676c0d22b Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 21 Sep 2026 11:14:20 +0900
Subject: [PATCH 40/45] test(ui): lock unavailable Save help contract
---
apps/desktop/src/App.unavailableSave.test.tsx | 35 +++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 apps/desktop/src/App.unavailableSave.test.tsx
diff --git a/apps/desktop/src/App.unavailableSave.test.tsx b/apps/desktop/src/App.unavailableSave.test.tsx
new file mode 100644
index 000000000..ede432f26
--- /dev/null
+++ b/apps/desktop/src/App.unavailableSave.test.tsx
@@ -0,0 +1,35 @@
+import { fireEvent, render, screen } from "@testing-library/react";
+import { expect, it, vi } from "vitest";
+import { App } from "./App";
+
+vi.mock("./features/score/pdfjs", () => ({
+ configureScorePdfWorker: vi.fn(),
+ loadScorePdf: vi.fn(() => ({
+ promise: Promise.resolve({ numPages: 1, getPage: vi.fn() }),
+ destroy: vi.fn(() => Promise.resolve())
+ }))
+}));
+
+it("keeps unavailable Save focusable without native-title-only help", () => {
+ const languageSpy = vi.spyOn(window.navigator, "language", "get").mockReturnValue("en-US");
+
+ try {
+ render( );
+
+ const saveButton = screen.getByRole("button", { name: /save project/i });
+ expect(saveButton).toHaveAttribute("aria-disabled", "true");
+ expect(saveButton).not.toHaveAttribute("disabled");
+ expect(saveButton).not.toHaveAttribute("title");
+
+ const descriptionId = saveButton.getAttribute("aria-describedby");
+ expect(descriptionId).toBeTruthy();
+ expect(document.getElementById(descriptionId!)).toHaveTextContent(
+ "Analyze a song to enable saving"
+ );
+
+ fireEvent.click(saveButton);
+ expect(saveButton).toHaveAttribute("aria-disabled", "true");
+ } finally {
+ languageSpy.mockRestore();
+ }
+});
From c53d30356e8b9efc9177a07e593c3f520c1cea38 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 21 Sep 2026 11:16:26 +0900
Subject: [PATCH 41/45] fix(ui): describe unavailable Save without native title
---
apps/desktop/src/App.tsx | 34 +++++++++++++++++++++++-----------
1 file changed, 23 insertions(+), 11 deletions(-)
diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx
index 110b3aa77..42b28cb9e 100644
--- a/apps/desktop/src/App.tsx
+++ b/apps/desktop/src/App.tsx
@@ -800,17 +800,29 @@ export function App() {
{t("saveProject")}
) : (
-
-
- {t("saveProject")}
-
+ <>
+
+
+ }
+ >
+
+ {t("saveProject")}
+
+ {t("saveRequiresAnalysis")}
+
+
+ {t("saveRequiresAnalysis")}
+
+ >
)}
Date: Mon, 21 Sep 2026 11:17:37 +0900
Subject: [PATCH 42/45] docs(ui): trace unavailable Save accessibility contract
---
docs/doctoring/sidebar-disabled-tooltips.md | 48 ++++++++++++---------
1 file changed, 27 insertions(+), 21 deletions(-)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 16b9236c1..21c94508b 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -1,60 +1,66 @@
-# Sidebar disabled-tooltip accessibility boundary
+# App unavailable-tooltip accessibility boundary
## Problem and buyer impact
-The Settings and Help controls are intentionally unavailable but remain discoverable in the sidebar. Native `title` text is not the product authority for the unavailable state: the icon-only triggers need an accessible name independent of tooltip rendering, activation must remain suppressed while `aria-disabled="true"` keeps the controls in the focus order, and tooltip copy must remain readable when translated text expands or the viewport narrows.
+Several App controls are intentionally unavailable while prerequisite product state is missing. Settings and Help remain discoverable in the sidebar, Import and Export are unavailable navigation entries, and Save is unavailable until a rehearsal song exists. Native `title` text is not the product authority for any of these states: controls need an accessible action name independent of tooltip rendering, activation must remain suppressed while `aria-disabled="true"` keeps them in the focus order, and the reason for an unavailable buyer action must remain available to assistive technology without depending on hover.
-The shared Tooltip primitive also used an opacity transition for appearance/disappearance without an explicit reduced-motion override. The effect is small, but it is decorative rather than necessary to convey state, so the primitive should respect the operating-system/browser reduced-motion preference.
+The shared Tooltip primitive also used an opacity transition for appearance/disappearance without an explicit reduced-motion override. The effect is decorative rather than necessary to convey state, so the primitive respects the operating-system/browser reduced-motion preference.
## Constraints
-- Keep Settings and Help unavailable; this slice does not implement either feature.
-- Keep the controls keyboard-discoverable and expose the same localized unavailable-state text as the accessible name and tooltip explanation.
-- `aria-disabled` is semantic state, not an interaction lock. Existing `preventUnavailableAction` remains responsible for suppressing pointer- and keyboard-generated activation.
+- Keep unavailable features unavailable; this slice does not implement Settings, Help, Import, Export, or a pre-analysis Save path.
+- Keep intentionally discoverable controls keyboard-focusable and expose a localized action name independently of Tooltip popup state.
+- `aria-disabled` is semantic state, not an interaction lock. `preventUnavailableAction` / `blockInactiveNavActivation` remain responsible for suppressing pointer- and keyboard-generated activation.
+- For Save, preserve `Save Project` as the action name and expose `Analyze a song to enable saving` as a persistent description as well as the visual Tooltip explanation. Do not collapse the reason into the action name.
- Do not create a second locale ledger or copy translation authority into the Tooltip primitive.
- Long translated text must wrap within the viewport instead of depending on a fixed English-sized popup.
-- The primitive must not claim browser, Narrator, VoiceOver, touch, or visual acceptance from jsdom/class assertions alone.
+- The primitive and jsdom tests must not claim browser, Narrator, VoiceOver, touch, or visual acceptance.
## Decision
-The sidebar uses the existing design-system `Tooltip`, with `TooltipTrigger` retaining `aria-disabled="true"`, a localized `aria-label`, and `preventUnavailableAction`. The shared popup gains a viewport-bounded maximum width, word breaking for long tokens, and `motion-reduce:transition-none` for its decorative opacity transition.
+The sidebar and unavailable navigation entries use the existing design-system `Tooltip`, with `TooltipTrigger` retaining `aria-disabled="true"`, a localized `aria-label`, and an explicit activation guard. The shared popup keeps a viewport-bounded maximum width, word breaking for long tokens, and `motion-reduce:transition-none` for its decorative opacity transition.
-A focused regression renders expanded German tooltip copy and pins the responsive/reduced-motion class contract. The same regression verifies the exact component-library integration: BandScope locks `@base-ui/react` 1.7.0, whose `TooltipTrigger` explicitly renders a `` by default and calls `useRenderElement('button', componentProps, ...)` with external element props in the merged prop list. The test therefore asserts the native `BUTTON` tag plus `type`, `aria-disabled`, class, and click-handler forwarding instead of applying Radix-specific `asChild` assumptions to Base UI.
+The unavailable Save control uses the same Tooltip system but preserves the existing styled `Button` through Base UI's `TooltipTrigger render={...}` composition. The rendered button remains focusable with `aria-disabled="true"`, has no native `title`, and references an always-present screen-reader-only explanation through `aria-describedby`. The Tooltip repeats that localized reason visually for pointer/focus discovery. The successful Save path is unchanged.
-This is deliberately a component contract rather than a product-level accessibility acceptance test.
+A focused Save regression first locked the missing contract: focusable `aria-disabled`, no native `title`, and a persistent localized description. The production repair then composed Tooltip + Button without changing Save activation authority. Existing Tooltip resilience tests continue to verify Base UI native-button/prop forwarding and responsive/reduced-motion class contracts.
+
+This remains a component/source contract rather than product-level accessibility acceptance.
Alternatives rejected:
-- Native `disabled`: removes these currently unavailable controls from ordinary keyboard focus and defeats the chosen discoverability contract.
+- Native `disabled`: removes these intentionally discoverable controls from ordinary keyboard focus.
- `aria-disabled` without an event guard: exposes state but leaves activation behavior enabled.
-- Tooltip-only naming: makes the accessible name depend on popup behavior rather than the trigger itself.
-- Radix `asChild` repair: BandScope does not use Radix Tooltip here. The locked Base UI 1.7.0 trigger already renders the native button and forwards the supplied props.
+- Native `title`: hover-dependent, inconsistent with the shared product Tooltip, and not a sufficient persistent explanation for the unavailable Save state.
+- Tooltip-only explanation: makes the Save reason depend on popup state; the persistent `aria-describedby` alternative remains available when the popup is absent.
+- Replacing `Save Project` with the prerequisite reason as the accessible name: loses the action identity instead of describing why that action is unavailable.
+- Radix `asChild` repair: BandScope uses Base UI Tooltip. Base UI supports composing a trigger with another rendered component via `render`.
- Fixed popup width sized for English/Korean: does not address CJK/European-language expansion or narrow viewports.
- Removing all Tooltip animation globally: unnecessary; honoring the user preference is the narrower control.
## Evidence and claim boundary
-MDN states that `aria-disabled="true"` communicates disabled semantics but does not suppress functionality; developers must suppress behavior themselves. W3C WCAG 2.2 SC 1.4.10 explains that ordinary text content should reflow within a viewport rather than force two-dimensional scrolling. MDN defines `prefers-reduced-motion` as the user preference for reducing non-essential motion. These sources support the chosen interaction, wrapping, and motion-preference boundaries; they do not prove BandScope conformance by themselves.
+MDN states that `aria-disabled="true"` communicates disabled semantics but does not suppress functionality; developers must suppress behavior themselves. W3C WCAG 2.2 SC 1.4.10 explains that ordinary text content should reflow within a viewport rather than force two-dimensional scrolling. MDN defines `prefers-reduced-motion` as the user preference for reducing non-essential motion. These sources support the interaction, wrapping, and motion-preference boundaries; they do not prove BandScope conformance by themselves.
-The repository lockfile identifies `@base-ui/react` 1.7.0. Upstream tag `v1.7.0` documents `TooltipTrigger` as rendering a `` and implements it with `useRenderElement('button', componentProps, ...)`, including `elementProps` in the merged props. A stale review that reasoned from Radix semantics was therefore dismissed as factually inapplicable after adding a repository regression for native-button/prop forwarding. Dismissing that stale finding is not an approval and does not satisfy the current-head review gate.
+The repository lockfile on this stack identifies `@base-ui/react` 1.7.0. The Base UI trigger contract renders a button by default and supports the `render` composition used for the unavailable Save Button. Earlier review that reasoned from Radix-specific semantics was dismissed only after the repository added Base UI trigger-forwarding evidence; that dismissal is not an approval and does not satisfy current-head review.
-Current automated evidence covers DOM semantics already present in `App.test.tsx`, Base UI trigger forwarding, and the Tooltip class contract. Current-head browser geometry, actual hover/focus popup placement, forced-colors behavior, Narrator/VoiceOver announcements, pointer/touch behavior, 400% zoom, and KO/EN/JA/ZH/VI/ES/DE/FR rendered acceptance remain separate UI Delivery Gate evidence.
+Current automated evidence covers App DOM semantics, the focused unavailable-Save description contract, Base UI trigger forwarding, and the Tooltip class contract. Current-head browser geometry, actual hover/focus/Escape popup behavior, forced-colors, Narrator/VoiceOver announcements, pointer/touch behavior, 400% zoom, and KO/EN/JA/ZH/VI/ES/DE/FR rendered acceptance remain separate UI Delivery Gate evidence.
## TRACEABILITY
-- Sidebar product integration: `apps/desktop/src/App.tsx`
+- App product integration: `apps/desktop/src/App.tsx`
+- Focused unavailable-Save regression: `apps/desktop/src/App.unavailableSave.test.tsx`
+- Existing App integration regressions: `apps/desktop/src/App.test.tsx`
- Shared primitive: `apps/desktop/src/components/ui/tooltip.tsx`
-- Existing focusable unavailable-control contract: `apps/desktop/src/App.test.tsx`
- Trigger-forwarding and expanded-copy/reduced-motion regression: `apps/desktop/src/components/ui/tooltip.resilience.test.tsx`
- Reviewer learning note: `.jules/palette.md`
-- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0
-- Upstream implementation: `mui/base-ui` tag `v1.7.0`, `packages/react/src/tooltip/trigger/TooltipTrigger.tsx`
+- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0 on this stack
+- Upstream implementation authority: Base UI Tooltip/Button render composition
## Security Notes
### Trust boundary
-The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip and localized in-process strings.
+The MDN, W3C, and upstream Base UI URLs in this doctoring note are documentation references only. BandScope does not fetch, execute, embed, or navigate to them at runtime, and this Tooltip change adds no network request, WebView navigation, subprocess, IPC, updater, model-download, credential, or trust-boundary path. Runtime behavior remains limited to the existing local design-system Tooltip, Button, and localized in-process strings.
## References
From ba88931e076161341381c3c16ebaa1e50a5bf1d0 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 21 Sep 2026 11:19:27 +0900
Subject: [PATCH 43/45] docs(ui): ground Save description in WAI-ARIA
---
docs/doctoring/sidebar-disabled-tooltips.md | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/docs/doctoring/sidebar-disabled-tooltips.md b/docs/doctoring/sidebar-disabled-tooltips.md
index 21c94508b..c93b77bdb 100644
--- a/docs/doctoring/sidebar-disabled-tooltips.md
+++ b/docs/doctoring/sidebar-disabled-tooltips.md
@@ -39,7 +39,9 @@ Alternatives rejected:
## Evidence and claim boundary
-MDN states that `aria-disabled="true"` communicates disabled semantics but does not suppress functionality; developers must suppress behavior themselves. W3C WCAG 2.2 SC 1.4.10 explains that ordinary text content should reflow within a viewport rather than force two-dimensional scrolling. MDN defines `prefers-reduced-motion` as the user preference for reducing non-essential motion. These sources support the interaction, wrapping, and motion-preference boundaries; they do not prove BandScope conformance by themselves.
+WAI-ARIA 1.3 distinguishes an accessible name from a more verbose accessible description and recommends `aria-describedby` when a short description already exists in the DOM. W3C Authoring Practices likewise documents a button referencing sibling descriptive text with `aria-describedby` and notes that `title` is a lower-priority fallback that can be inaccessible to users without a hover-capable pointing device. That directly supports keeping `Save Project` as the action name while exposing the prerequisite reason as a separate persistent description.
+
+MDN states that `aria-disabled="true"` communicates disabled semantics but does not suppress functionality; developers must suppress behavior themselves. W3C WCAG 2.2 SC 1.4.10 explains that ordinary text content should reflow within a viewport rather than force two-dimensional scrolling. MDN defines `prefers-reduced-motion` as the user preference for reducing non-essential motion. These sources support the interaction, description, wrapping, and motion-preference boundaries; they do not prove BandScope conformance by themselves.
The repository lockfile on this stack identifies `@base-ui/react` 1.7.0. The Base UI trigger contract renders a button by default and supports the `render` composition used for the unavailable Save Button. Earlier review that reasoned from Radix-specific semantics was dismissed only after the repository added Base UI trigger-forwarding evidence; that dismissal is not an approval and does not satisfy current-head review.
@@ -55,6 +57,7 @@ Current automated evidence covers App DOM semantics, the focused unavailable-Sav
- Reviewer learning note: `.jules/palette.md`
- Dependency authority: root `package-lock.json`, `@base-ui/react` 1.7.0 on this stack
- Upstream implementation authority: Base UI Tooltip/Button render composition
+- Accessibility description authority: WAI-ARIA 1.3 and W3C Authoring Practices accessible-name/description guidance
## Security Notes
@@ -68,4 +71,8 @@ Mozilla Developer Network. (2025, November 6). *ARIA: aria-disabled attribute*.
Mozilla Developer Network. (2026, June 10). *prefers-reduced-motion CSS media feature*. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
+World Wide Web Consortium. (2026, June 4). *Accessible Rich Internet Applications (WAI-ARIA) 1.3*. https://www.w3.org/TR/2026/WD-wai-aria-1.3-20260604/
+
+World Wide Web Consortium, Web Accessibility Initiative. (n.d.). *Providing accessible names and descriptions*. Retrieved September 21, 2026, from https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/
+
W3C Accessibility Guidelines Working Group. (2026, August 10). *Understanding Success Criterion 1.4.10: Reflow*. W3C Web Accessibility Initiative. https://www.w3.org/WAI/WCAG22/Understanding/reflow
From 6b88c72bb48609f19fbf0a803ab820079b48b51e Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 23 Sep 2026 00:07:17 +0900
Subject: [PATCH 44/45] test(ui): absorb App tooltip title evidence
---
apps/desktop/src/App.unavailableSave.test.tsx | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/apps/desktop/src/App.unavailableSave.test.tsx b/apps/desktop/src/App.unavailableSave.test.tsx
index ede432f26..60de27ea7 100644
--- a/apps/desktop/src/App.unavailableSave.test.tsx
+++ b/apps/desktop/src/App.unavailableSave.test.tsx
@@ -33,3 +33,26 @@ it("keeps unavailable Save focusable without native-title-only help", () => {
languageSpy.mockRestore();
}
});
+
+it("keeps App icon tooltip triggers free of native title fallbacks", () => {
+ const languageSpy = vi.spyOn(window.navigator, "language", "get").mockReturnValue("en-US");
+
+ try {
+ render( );
+
+ const settingsButton = screen.getByRole("button", { name: /settings coming soon/i });
+ const helpButton = screen.getByRole("button", { name: /help coming soon/i });
+ expect(settingsButton).not.toHaveAttribute("title");
+ expect(helpButton).not.toHaveAttribute("title");
+
+ const youtubeInput = screen.getByRole("textbox", { name: /youtube url/i });
+ fireEvent.change(youtubeInput, {
+ target: { value: "https://youtube.com/watch?v=abc123DEF45" }
+ });
+
+ const clearButton = screen.getByRole("button", { name: /clear youtube url/i });
+ expect(clearButton).not.toHaveAttribute("title");
+ } finally {
+ languageSpy.mockRestore();
+ }
+});
From 5589cbd50d9791523c957b9cf4d07e7f2bf24685 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 24 Sep 2026 15:06:49 +0000
Subject: [PATCH 45/45] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20tooltips?=
=?UTF-8?q?=20explaining=20disabled=20Settings=20and=20Help=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit