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 body40 - Semantics —
role="dialog",aria-label="Command palette",aria-modal="true"; inputaria-label="Search commands"; listrole="listbox"+aria-label="Command results", optionsrole="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
requestAnimationFrameso iOS keyboard pushes content - Recent-item actions —
navigate(href),assistant(prompt + source viadispatchOshunAssistantOpen),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-labels. 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 viatrackSearchResultOpened
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; widthsEXPANDED = 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 };AssistantContextHandoffsent 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); audiencecustomer | 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 fromlilith.css, but most overlays predate and bypass the native element
Cross-references#
- 01-app-shell.md — header triggers + mobile bottom nav
- 06-keyboard-a11y.md — global shortcuts + focus
- 11-design-system-tokens.md — overlay chrome
- Customer view:
customer/02-home-discovery/messages.md(InProductHelp)
Open questions / known gaps#
-
QuickActionsTrayPanel,WidgetSidebar,DeepLinkBuilderhave no customer-surface mount sites — pending integration or vestigial? - No shared
Dialog/Modal/Sheetprimitive — 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
-
SmartAppBannerhas no explicit z-index; verify stacking on welcome routes when other overlays are also mounted -
UniversalSearchPanelvsCommandPaletteoverlap in mental space — document which is canonical for which entry point