# Shell: Overlays and panels

Source: `apps/oshun/web/src/components/command-palette/CommandPalette.tsx`
(re-exported as `components/CommandPalette.tsx`),
`components/UniversalSearchPanel.tsx`,
`components/assistant-dock/AssistantDock.tsx`,
`components/assistant/AssistantPanel.tsx`, `components/WhatsNewDropdown.tsx`,
`components/CrossDomainQuickActionsMenu.tsx`,
`components/QuickActionsTrayPanel.tsx`, `components/WidgetSidebar.tsx`,
`components/InProductHelp.tsx`, `components/DeepLinkBuilder.tsx`,
`components/SmartAppBanner.tsx`, `components/ShellLayout.tsx`,
`navigation/assistant-entry.ts` (all under `apps/oshun/web/src/`)

Every cross-cutting overlay the customer can summon from any route. There is
**no shared `Dialog` / `Modal` / `Sheet` primitive** — each overlay hand-rolls
its own open-state, focus management, dismissal, and z-index. Walk this whenever
a new overlay is added or the z-stack changes.

## Command palette

`components/command-palette/CommandPalette.tsx`. Mounted by `ShellLayout` on all
non-admin shells: `<CommandPalette activeRoute={active} />`.

- [ ] **Trigger** — `⌘K` / `Ctrl+K`; `/` focuses shell-search input instead
- [ ] **z-index** — backdrop `30`, palette body `40`
- [ ] **Semantics** — `role="dialog"`, `aria-label="Command palette"`,
      `aria-modal="true"`; input `aria-label="Search commands"`; list
      `role="listbox"` + `aria-label="Command results"`, options `role="option"`
- [ ] **Categories** —
      `recent | navigation | domain | search | create |     agent | jump | quick-action`
      (`CATEGORY_LABELS`)
- [ ] **Recents** persisted to `localStorage['oshun-cmd-recent']` (queries,
      max 5) + `oshun-cmd-recent-items` (items, max 8)
- [ ] **Keyboard nav** — Up/Down moves selection; Enter activates; Esc closes
- [ ] **Mobile** — full-screen overlay; input focused via
      `requestAnimationFrame` so iOS keyboard pushes content
- [ ] **Recent-item actions** — `navigate` (href), `assistant` (prompt + source
      via `dispatchOshunAssistantOpen`), `query` (search text)

## Universal search panel

`components/UniversalSearchPanel.tsx`. Mounted inline by `HomeWorkspace`. It is
a **section, not a modal** — `<section aria-label="Universal search">`, input +
clear `aria-label`s. Filters `SEARCH_DOMAIN_FILTERS` + `all`; recents at
`localStorage['oshun-search-recent']` (max 5). Cross-domain groups via
`SEARCH_CROSS_DOMAIN_CONNECTIONS` shown when ≥2 active domains in results.

- [ ] **BFF call** — `buildOshunBffUrl(...)` + `resolveBffAuthToken()`
- [ ] **Save to library** — `toggleOshunWebLibraryItem` +
      `trackLibraryItemSaved` / `Unsaved`; open via `trackSearchResultOpened`

## Assistant dock + panel

`components/assistant-dock/AssistantDock.tsx` +
`components/assistant/AssistantPanel.tsx`. Opened from anywhere via
`dispatchOshunAssistantOpen()` (custom event `oshun:assistant-open`); closed via
`dispatchOshunAssistantClose()`.

- [ ] **Dock mode** persisted to `localStorage[ASSISTANT_DOCK_MODE_KEY]` =
      `closed | collapsed | expanded`; widths `EXPANDED = 360`,
      `COLLAPSED = 128`
- [ ] **Mount threshold** — `ASSISTANT_DOCK_MIN_WIDTH = 1360`; below, the
      assistant opens as an overlay panel not the right-rail dock
- [ ] **Layout** — `<aside data-shell-assistant-dock>` `position: fixed`,
      `top: <header offset>`, `right: 20`, `bottom: 20`, **`zIndex: 11`**
- [ ] **Collapsed** view shows thread + stage + entry labels + Expand CTA;
      **expanded** mounts `AssistantPanel presentation="dock"`
- [ ] **Open event payload** — `{ source, prompt, requestedModeId }`;
      `AssistantContextHandoff` sent when a domain page transfers state

## What's-new dropdown

`components/WhatsNewDropdown.tsx`. Mounted by `ShellLayout` (top nav on desktop,
mobile menu on mobile). Header trigger with `aria-expanded` + `aria-haspopup`;
dropdown at `position: absolute`, `width: 380`, `maxHeight: 480`,
**`zIndex: 20`**. Entry types `feature | improvement | fix` (+ optional
`domain`, `href`); pulsing terracotta unread dot when entries newer than
last-seen. Open animation `oshun-fade-in-down`; close `oshun-scale-out`;
click-outside + Esc dismiss.

## Cross-domain quick-actions menu

`components/CrossDomainQuickActionsMenu.tsx`. Mounted by `ShellLayout` and by
`ExploreDashboard`. Trigger variants `dock` (header) and `panel` (in-page).
`QUICK_ACTION_PRESETS` define
`domainId + section ('center' | 'companions') + eyebrow + title + summary + path + icon`.
Unavailability handled by `isDomainTemporarilyUnavailable` +
`buildUnavailableDomainLaunchPath`. Mounts via `createPortal`; `onOpen` +
`onNavigate` telemetry callbacks.

## Quick-actions tray panel

`components/QuickActionsTrayPanel.tsx`. Defined, no current customer-surface
mount (see Open questions). Surfaces `home | explore`; backdrop `zIndex: 30`,
tray `40`; actions carry `domain (DomainName | null)` for accent (falls back to
`L.accent`); ripple from click origin.

## Widget sidebar

`components/WidgetSidebar.tsx`. Defined, no current mount (see Open questions).
280 px right rail. Widgets: `streak | habits | timer | calendar | stats | quote`
(default visible: streak, habits, timer, stats). Controls via `open` +
`onOpenChange`; per-widget visibility toggle in settings popover; `GripVertical`
reorder.

## In-product help

`components/InProductHelp.tsx`. Mounted ad-hoc by routes that opt in (e.g.
`app/messages/page.tsx`).

- [ ] **Trigger** — Help button with `aria-haspopup="dialog"` + `aria-expanded`
- [ ] **Panel** `<aside role="dialog" aria-label="In-product help">`
      (non-modal); **walkthrough overlay**
      `<div role="dialog" aria-modal="true" aria-label="Walkthrough">`
- [ ] **Fetch** `/api/help/contextual?surface=<pathname>&audience=<…>` (loading
      / error / loaded); audience `customer | admin | creator | partner`;
      sections cover tooltips, walkthroughs, videos, articles

## Deep-link builder

`components/DeepLinkBuilder.tsx`. Defined; customer-surface mounts not yet
wired. Builds `https://oshun.app/<domain>/<contentType>/<id>` URLs. Domain
options `tara | veritas | nyx | arete`; contentType
`session | article | observation | habit | goal`. `buildUrl` encodes
`contentId`, returns empty when any part missing. Share via `tryNativeShare()`
(`navigator.share`), falls back to clipboard with `Check` icon for 2000 ms.

## Smart app banner

`components/SmartAppBanner.tsx`. Mounted by welcome / download / domains public
routes (`app/welcome/*/page.tsx`). Detects platform from UA (`iphone|ipad|ipod`
→ `ios`; `android` → `android`; else `web`), per-platform store URL + scheme
`oshun://` + action label. Dismiss persisted to
`localStorage['oshun_smart_banner_dismissed']` (7-day cooldown). Slide-down on
entrance, slide-up on close; `forcePlatform` prop for tests. No explicit z-index
— verify no collision on welcome routes.

## Focus, dismissal, z-index

Each overlay hand-rolls these — no central manager. **z-stack** has no central
scale; observed: `AssistantDock 11`, `WhatsNewDropdown 20`, `CommandPalette`
backdrop `30` / body `40`, `QuickActionsTrayPanel` backdrop `30` / body `40`.
Modal-style overlays share `30/40` and are not designed to stack; the assistant
dock sits below intentionally (persistent rail).

- [ ] **Open focus** — `requestAnimationFrame(() => inputRef.current?.focus())`
- [ ] **Esc** — each component wires its own
      `document.addEventListener('keydown', …)`; closes topmost overlay
- [ ] **Backdrop click** — palette + tray dismiss; dropdowns use click-outside
      on the popover
- [ ] **Focus trap** — palette traps Tab in `handleInputKeyDown`; others mostly
      do not trap; most overlays also do NOT restore focus to opener
- [ ] **Native `<dialog>`** picks up Lilith chrome from `lilith.css`, but most
      overlays predate and bypass the native element

## Cross-references

- [01-app-shell.md](./01-app-shell.md) — header triggers + mobile bottom nav
- [06-keyboard-a11y.md](./06-keyboard-a11y.md) — global shortcuts + focus
- [11-design-system-tokens.md](./11-design-system-tokens.md) — overlay chrome
- Customer view: `customer/02-home-discovery/messages.md` (`InProductHelp`)

## Open questions / known gaps

- [ ] `QuickActionsTrayPanel`, `WidgetSidebar`, `DeepLinkBuilder` have no
      customer-surface mount sites — pending integration or vestigial?
- [ ] No shared `Dialog` / `Modal` / `Sheet` primitive — every overlay
      reimplements semantics, focus trap, Esc, backdrop click. Consolidate or
      document the policy by reference
- [ ] Return-focus discipline missing on most overlays; walk each with
      screen-reader to catch regressions
- [ ] Z-index scale (11, 20, 30, 40) hand-picked with no central contract; move
      to a token map or document explicitly
- [ ] `SmartAppBanner` has no explicit z-index; verify stacking on welcome
      routes when other overlays are also mounted
- [ ] `UniversalSearchPanel` vs `CommandPalette` overlap in mental space —
      document which is canonical for which entry point
