From d3708c1e63f5161d1143b780f497c6f4b671f58a Mon Sep 17 00:00:00 2001 From: Reece Browne <74901996+reecebrowne@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:02:47 +0000 Subject: [PATCH] Highlight the rail entry whose tool is open (#7723) --- .../shared/quickNav/QuickNavHostBridge.tsx | 3 ++ .../shared/quickNav/QuickNavRailHost.tsx | 4 +++ .../contexts/QuickNavHostContext.test.tsx | 31 +++++++++++++++++++ .../src/core/contexts/QuickNavHostContext.tsx | 7 +++++ frontend/editor/src/core/pages/HomePage.tsx | 1 + 5 files changed, 46 insertions(+) diff --git a/frontend/editor/src/core/components/shared/quickNav/QuickNavHostBridge.tsx b/frontend/editor/src/core/components/shared/quickNav/QuickNavHostBridge.tsx index 9a9101a826..d97674464e 100644 --- a/frontend/editor/src/core/components/shared/quickNav/QuickNavHostBridge.tsx +++ b/frontend/editor/src/core/components/shared/quickNav/QuickNavHostBridge.tsx @@ -22,6 +22,7 @@ export interface QuickNavHostBridgeProps { requestNavigation?: (go: () => void) => void; onGoToDefaultState?: () => void; onSelectTool?: (toolId: ToolId) => void; + activeTool?: ToolId | null; /** Merged over the reasons worked out here, for what only the app can see. */ toolReasons?: QuickNavToolReasons; } @@ -34,6 +35,7 @@ export function QuickNavHostBridge({ onOpenSettings, requestNavigation, onSelectTool, + activeTool = null, onGoToDefaultState, toolReasons, }: QuickNavHostBridgeProps) { @@ -59,6 +61,7 @@ export function QuickNavHostBridge({ signingBadge, portalAccess, readerMode, + activeTool, notificationsOpen, toolReasons: mergedToolReasons, }, diff --git a/frontend/editor/src/core/components/shared/quickNav/QuickNavRailHost.tsx b/frontend/editor/src/core/components/shared/quickNav/QuickNavRailHost.tsx index 3fba4d7e1f..e7a1b6e8c6 100644 --- a/frontend/editor/src/core/components/shared/quickNav/QuickNavRailHost.tsx +++ b/frontend/editor/src/core/components/shared/quickNav/QuickNavRailHost.tsx @@ -48,6 +48,8 @@ export function QuickNavRailHost() { else go(route); }; + const openingTool = (id: ToolId) => ({ current: host?.activeTool === id }); + const unusable = (id: ToolId) => { const reason = host?.toolReasons?.[id]; return { disabled: Boolean(reason), reason }; @@ -135,6 +137,7 @@ export function QuickNavRailHost() { icon: ( ), + ...openingTool("automate"), ...unusable("automate"), onClick: () => openTool("automate", "/automate"), }, @@ -146,6 +149,7 @@ export function QuickNavRailHost() { ), badge: host?.signingBadge, badgeTone: "warning", + ...openingTool("sharedSign"), ...unusable("sharedSign"), onClick: () => openTool("sharedSign", "/shared-sign"), }, diff --git a/frontend/editor/src/core/contexts/QuickNavHostContext.test.tsx b/frontend/editor/src/core/contexts/QuickNavHostContext.test.tsx index fc648cbfe5..0a348262fa 100644 --- a/frontend/editor/src/core/contexts/QuickNavHostContext.test.tsx +++ b/frontend/editor/src/core/contexts/QuickNavHostContext.test.tsx @@ -13,6 +13,7 @@ function Probe({ onRead }: { onRead: (value: unknown) => void }) { appMounted: host?.appMounted, chromeless: host?.chromeless, identity: host?.identity, + activeTool: host?.activeTool, openSettings: Boolean(host?.actions.current?.openSettings), }); return null; @@ -26,6 +27,11 @@ function App() { return null; } +function AppWithTool({ tool }: { tool: "automate" | null }) { + useRegisterQuickNavHost({ activeTool: tool }, {}); + return null; +} + function LoginRoute() { useSuppressQuickNavRail(); return null; @@ -71,6 +77,31 @@ describe("QuickNavHostContext", () => { expect(after.openSettings).toBe(false); }); + it("clears the open tool when the next app registers without one", () => { + let latest: Record = {}; + const view = render( + + (latest = value as Record)} + /> + + , + ); + expect(latest.activeTool).toBe("automate"); + + act(() => { + view.rerender( + + (latest = value as Record)} + /> + + , + ); + }); + expect(latest.activeTool).toBe(null); + }); + it("hides the bar while a route with no app chrome is on screen", () => { // appMounted is sticky, so it can't answer "is an app on screen now". const { view, read } = setup(); diff --git a/frontend/editor/src/core/contexts/QuickNavHostContext.tsx b/frontend/editor/src/core/contexts/QuickNavHostContext.tsx index 540ec8cd6c..1a19cffc6f 100644 --- a/frontend/editor/src/core/contexts/QuickNavHostContext.tsx +++ b/frontend/editor/src/core/contexts/QuickNavHostContext.tsx @@ -24,6 +24,7 @@ export interface QuickNavHostData { signingBadge: number; portalAccess: boolean; readerMode: boolean; + activeTool: ToolId | null; /** The app owns the panel; the rail's bell only reports its state. */ notificationsOpen: boolean; /** Translated; absent means usable. */ @@ -61,6 +62,7 @@ const EMPTY_DATA: QuickNavHostData = { signingBadge: 0, portalAccess: false, readerMode: false, + activeTool: null, notificationsOpen: false, hasSettings: false, }; @@ -90,6 +92,7 @@ export function QuickNavHostProvider({ children }: { children: ReactNode }) { merged.signingBadge === prev.signingBadge && merged.portalAccess === prev.portalAccess && merged.readerMode === prev.readerMode && + merged.activeTool === prev.activeTool && merged.notificationsOpen === prev.notificationsOpen && merged.hasSettings === prev.hasSettings && merged.identity?.displayName === prev.identity?.displayName && @@ -143,6 +146,7 @@ export function useRegisterQuickNavHost( signingBadge, portalAccess, readerMode, + activeTool, notificationsOpen, toolReasons, } = data; @@ -155,6 +159,8 @@ export function useRegisterQuickNavHost( signingBadge: signingBadge ?? 0, portalAccess: portalAccess ?? false, readerMode: readerMode ?? false, + // Cleared, not omitted as toolReasons is: a stale tool marks an entry. + activeTool: activeTool ?? null, notificationsOpen: notificationsOpen ?? false, // Omitted when unknown, so the last answer survives a re-fetch. ...(toolReasons ? { toolReasons } : {}), @@ -168,6 +174,7 @@ export function useRegisterQuickNavHost( signingBadge, portalAccess, readerMode, + activeTool, notificationsOpen, toolReasons, hasSettings, diff --git a/frontend/editor/src/core/pages/HomePage.tsx b/frontend/editor/src/core/pages/HomePage.tsx index efb6af40d2..6434acbc9e 100644 --- a/frontend/editor/src/core/pages/HomePage.tsx +++ b/frontend/editor/src/core/pages/HomePage.tsx @@ -525,6 +525,7 @@ export default function HomePage() { onSetReaderMode={setReaderMode} onGoToDefaultState={goToDefaultState} onSelectTool={handleToolSelect} + activeTool={selectedToolKey} toolReasons={quickNavToolReasons} />