diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 3dfcd481de..d93474c171 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -233,6 +233,12 @@ other header-adjacent notices must not fork assistant open state, gate on AI runtime fetches, or move assistant availability logic out of `frontend-modern/src/stores/aiChat.ts` and `frontend-modern/src/useAppRuntimeState.ts` just because they share the same authenticated shell. +That same shared shell boundary must respect blocking modal ownership. +`frontend-modern/src/App.tsx` and `frontend-modern/src/AppLayout.tsx` may use +the shared dialog runtime to hide the closed assistant launcher and close the +drawer while a blocking shared dialog is open, but they must not leave Pulse +Assistant interactive behind a modal or fork a second assistant-open state +model to do it. That same shared shell rule applies when presentation policy suppresses hosted organization chrome: `frontend-modern/src/App.tsx` and `frontend-modern/src/AppLayout.tsx` may hide org switchers or demo-only org @@ -284,9 +290,9 @@ helpers used across chat, settings, and usage surfaces. `frontend-modern/src/utils/aiSessionDiffPresentation.ts`, and `frontend-modern/src/utils/aiExplorePresentation.ts` are the canonical owners for provider naming, provider health labels, control-level semantics, -chat drawer title/subtitle, discovery hint framing, chat/session empty states, -assistant message and question-card labels, session-diff badges, and explore- -status labels. +chat drawer title/subtitle, launcher title/aria copy, session-menu labeling, +discovery hint framing, chat/session empty states, assistant message and +question-card labels, session-diff badges, and explore-status labels. Settings and chat surfaces must consume those helpers instead of keeping local AI wording or model/provider inference branches. diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 24db7e4029..5ba67dbc3f 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -950,6 +950,9 @@ theme synchronization, and authenticated runtime startup, and as org switching and kiosk-safe navigation. Future hosted browser bootstrap work must extend that split rather than pulling org bootstrap and app chrome back into one monolithic route component. +That same authenticated shell split must also respect shared blocking dialogs: +hosted chrome may not leave the Pulse Assistant launcher or an already-open +assistant drawer interactive behind a modal that currently owns the viewport. That same authenticated shell contract now also has to distinguish backend availability from websocket-stream liveness. When hosted runtime health stays available during a stream reconnect or renegotiation window, diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 18f217dcf9..e3d5ed0a40 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -265,6 +265,11 @@ work extends shared components instead of creating new local variants. shared drawer shell owner for assistant open/close state, focus handoff, and tenant-local context/session persistence; the app shell must not fork that state across `App.tsx`, `AppLayout.tsx`, or page-level helpers. + The same shared shell boundary must keep Pulse Assistant coherent while a + blocking shared dialog owns the viewport: closed launcher affordances must + hide until the dialog clears, and the shell must close any already-open + assistant drawer instead of leaving background assistant controls visibly + active behind the modal. AI-owned frontend surfaces that need shared settings or model-catalog truth must route those reads through `frontend-modern/src/stores/aiRuntimeState.ts` rather than each feature @@ -1063,11 +1068,16 @@ pushing tone or active-card presentation logic back into the shell. The shared dialog now follows that same owner split. `frontend-modern/src/components/shared/Dialog.tsx` stays the render shell, `frontend-modern/src/components/shared/useDialogState.ts` owns focus trap, -body-scroll lock, previous-focus restoration, and backdrop-close runtime, and +body-scroll lock, previous-focus restoration, shared blocking-dialog +visibility, and backdrop-close runtime, and `frontend-modern/src/components/shared/dialogModel.ts` owns focusable-element lookup plus layout and panel class policy. Future dialog work should extend those owners instead of pushing focus-trap lifecycle or layout policy back into the shared shell. +App-shell consumers such as `frontend-modern/src/App.tsx` and +`frontend-modern/src/AppLayout.tsx` may read that shared blocking-dialog state +to suppress background affordances, but they must not reimplement their own +parallel modal-stack bookkeeping. The shared history chart now follows the same owner shape. `frontend-modern/src/components/shared/HistoryChart.tsx` stays the render shell, `frontend-modern/src/components/shared/useHistoryChartState.ts` owns diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 108f90a53d..477b698c6b 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -246,7 +246,7 @@ regression protection. the same compact summary payload instead of reintroducing route-local `all-resources` fetches, summary recomputation, or page-level layout churn that displaces the protected overview widgets. -32. Keep infrastructure summary consumers on the compact dashboard overview rather than reopening the all-resources hook. `frontend-modern/src/hooks/useDashboardTrends.ts`, `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts`, and adjacent dashboard summary consumers may derive chart identity and storage presence from the overview payload they were already given, but they must not call `useResources()` or mount a second unfiltered unified-resource fetch path inside the dashboard hot path. That rule also applies to globally mounted helpers such as `frontend-modern/src/components/AI/Chat/index.tsx`: closed assistant surfaces must read the live websocket snapshot or existing unified-resource cache rather than forcing the dashboard to pay for `all-resources` just because the shell component is mounted. When that assistant shell changes presentation, `frontend-modern/src/utils/aiChatPresentation.ts` must remain the canonical owner for drawer labels and empty-state copy so hot-path consumers do not grow one-off inline strings or extra state branches alongside the mounted shell. +32. Keep infrastructure summary consumers on the compact dashboard overview rather than reopening the all-resources hook. `frontend-modern/src/hooks/useDashboardTrends.ts`, `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts`, and adjacent dashboard summary consumers may derive chart identity and storage presence from the overview payload they were already given, but they must not call `useResources()` or mount a second unfiltered unified-resource fetch path inside the dashboard hot path. That rule also applies to globally mounted helpers such as `frontend-modern/src/components/AI/Chat/index.tsx`: closed assistant surfaces must read the live websocket snapshot or existing unified-resource cache rather than forcing the dashboard to pay for `all-resources` just because the shell component is mounted. When that assistant shell changes presentation, `frontend-modern/src/utils/aiChatPresentation.ts` must remain the canonical owner for launcher, drawer, session-menu, and empty-state copy so hot-path consumers do not grow one-off inline strings or extra state branches alongside the mounted shell. Blocking shared dialogs must also suppress closed assistant affordances through the shared dialog runtime instead of leaving the mounted shell clickable behind another overlay. 33. Keep hidden workload-route selector shells off the hot path. When the workloads route keeps `frontend-modern/src/components/shared/InfrastructureSelector.tsx` mounted only for layout parity, `frontend-modern/src/components/shared/useInfrastructureSelectorState.ts` diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index e161d9f8fa..106d5acd68 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -266,6 +266,10 @@ querying, and the operator-facing storage health presentation layer. may hide top-bar org chrome for public demo posture, but it must not leak into storage/recovery preview route ownership, first-session recovery copy, or route-level framing decisions. + That same shared app-shell boundary must also respect blocking shared + dialogs: background assistant affordances may hide while a modal owns the + viewport, but storage/recovery routes must not grow their own parallel + modal-stack bookkeeping just because they share `App.tsx`. 36. Keep public self-hosted purchase handoff and activation routes on the adjacent commercial/auth boundary. When `internal/api/router.go`, `internal/api/router_routes_cloud.go`, `internal/api/licensing_handlers.go`, diff --git a/frontend-modern/src/App.tsx b/frontend-modern/src/App.tsx index 5341bc1267..af2f26a3cd 100644 --- a/frontend-modern/src/App.tsx +++ b/frontend-modern/src/App.tsx @@ -22,6 +22,7 @@ import { MonitoredSystemLimitWarningBanner } from './components/shared/Monitored import { WhatsNewModal } from './components/shared/WhatsNewModal'; import { KeyboardShortcutsModal } from './components/shared/KeyboardShortcutsModal'; import { CommandPaletteModal } from './components/shared/CommandPaletteModal'; +import { dialogStackHasBlockingDialog } from './components/shared/useDialogState'; import { createTooltipSystem } from './components/shared/Tooltip'; import { TokenRevealDialog } from './components/TokenRevealDialog'; import { UpdateProgressModal } from './components/UpdateProgressModal'; @@ -304,6 +305,12 @@ function App() { onCloseCommandPalette: () => setCommandPaletteOpen(false), }); + createEffect(() => { + if (dialogStackHasBlockingDialog() && aiChatStore.isOpenSignal()) { + aiChatStore.close(); + } + }); + // Setup escape handling for the assistant drawer. onMount(() => { const handleKeyDown = (e: KeyboardEvent) => { diff --git a/frontend-modern/src/AppLayout.tsx b/frontend-modern/src/AppLayout.tsx index 429a3d0714..911d0412ad 100644 --- a/frontend-modern/src/AppLayout.tsx +++ b/frontend-modern/src/AppLayout.tsx @@ -21,6 +21,7 @@ import Minimize2Icon from 'lucide-solid/icons/minimize-2'; import ActivityIcon from 'lucide-solid/icons/activity'; import { MobileNavBar } from '@/components/shared/MobileNavBar'; import { ReleaseCandidateBanner } from '@/components/shared/ReleaseCandidateBanner'; +import { dialogStackHasBlockingDialog } from '@/components/shared/useDialogState'; import { OrgSwitcher } from '@/components/OrgSwitcher'; import { PulsePatrolLogo } from '@/components/Brand/PulsePatrolLogo'; import { MONITORING_READ_SCOPE } from '@/constants/apiScopes'; @@ -44,6 +45,10 @@ import { presentationPolicyHidesUpgradePrompts, presentationPolicyIsDemoMode, } from '@/stores/sessionPresentationPolicy'; +import { + AI_CHAT_LAUNCHER_ARIA_LABEL, + getAIChatLauncherTitle, +} from '@/utils/aiChatPresentation'; import type { AppConnectionStatus } from '@/useAppRuntimeState'; const ROOT_INFRASTRUCTURE_PATH = buildInfrastructurePath(); @@ -812,17 +817,20 @@ export function AppLayout(props: AppLayoutProps) { - +