diff --git a/.jules/palette.md b/.jules/palette.md
index c05638899..91300fb20 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,7 @@
-## 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:** `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/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..42b28cb9e 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 ? (
+ ) : (
+
+
+ {buttonContent}
+
+ {title && {title}}
+
);
})}
@@ -611,26 +630,30 @@ export function App() {