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}
/>