From fe1853b7775e5df33480329693dbd99c13f3a926 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 11 May 2026 16:16:51 -0700 Subject: [PATCH 01/12] docs(specs): chat sidenav + history search design (Phase 1+2) Sidenav composition with expanded/collapsed/drawer modes plus a modal history-search palette driven by Cmd/Ctrl+K. Hard-replaces chat-thread-drawer. Defers archive, projects, library, GPTs, apps, agents, group chats, and workspace switching to later phases. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...-chat-sidenav-and-history-search-design.md | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-11-chat-sidenav-and-history-search-design.md diff --git a/docs/superpowers/specs/2026-05-11-chat-sidenav-and-history-search-design.md b/docs/superpowers/specs/2026-05-11-chat-sidenav-and-history-search-design.md new file mode 100644 index 000000000..3c97b2873 --- /dev/null +++ b/docs/superpowers/specs/2026-05-11-chat-sidenav-and-history-search-design.md @@ -0,0 +1,322 @@ +# Chat sidenav + history search — design + +**Date:** 2026-05-11 +**Surface:** `@ngaf/chat` (`libs/chat/`) — new composition + new primitive; hard-replaces existing `chat-thread-drawer` composition +**Status:** Design approved; ready for implementation plan +**Research input:** ChatGPT authenticated left-sidenav PRD (pasted by user) + +## Summary + +Add a single sidenav composition that consolidates the existing thread-drawer behavior, supports three responsive modes (expanded / collapsed / drawer), and exposes named projection slots for sections the framework does not own (projects, library, GPTs, apps, agents, group chats, account/profile). Add a separate modal history-search palette primitive driven by Cmd/Ctrl+K. Together these cover **Phase 1 (shell + threads)** and **Phase 2 (search)** of the larger PRD decomposition. All other PRD sections (archive, projects, library, etc.) are deferred to later phases. + +This spec hard-replaces `chat-thread-drawer` rather than coexisting with it. The framework is pre-1.0 (0.0.x) and project policy is to break in patch releases without backward-compat shims. + +## Goals + +- Honest framework surface — express only what the framework can deliver today (threads + search). Everything else is consumer territory, exposed via named `` slots. +- One composition covers all three responsive modes. No drawer-only second class. +- Cmd/Ctrl+K opens a modal palette globally — works whether the sidenav is collapsed, in drawer-closed state, or not even rendered. +- Palette is "dumb" — consumer wires results, matching the existing primitive pattern (`chat-thread-list` takes `threads`, emits `threadSelected`). + +## Non-goals (deferred to later phases) + +- Archive semantics. No `status: 'active' | 'archived'` on the thread model. +- Per-row overflow menu (archive / delete / share / rename). Phase 3. +- Server-side or content search. Phase 3+; for now consumers wire whatever search they want. +- Projects, Library, GPTs, Apps, Agents, Group chats, Workspace switching, Temporary Chats as built-in framework concepts. These are consumer slots only. +- Persistence of expanded/collapsed state across reloads. +- A formal `SearchableAgent` interface. + +## Scope split + +Both phases ship in one branch / one PR. They are sequenced in implementation but tightly coupled (the sidenav emits `(searchOpened)`, the palette listens) — splitting them across PRs would force a temporary unwired state. + +- **Phase 1:** `chat-sidenav` composition, hard-replace `chat-thread-drawer`, migrate examples-chat-angular. +- **Phase 2:** `chat-history-search-palette` primitive, Cmd+K wiring on the sidenav composition, default wiring in examples-chat-angular. + +## File map + +**New:** +- `libs/chat/src/lib/compositions/chat-sidenav/chat-sidenav.component.ts` +- `libs/chat/src/lib/compositions/chat-sidenav/chat-sidenav.component.spec.ts` +- `libs/chat/src/lib/primitives/chat-history-search-palette/chat-history-search-palette.component.ts` +- `libs/chat/src/lib/primitives/chat-history-search-palette/chat-history-search-palette.component.spec.ts` +- `libs/chat/src/lib/styles/chat-sidenav.styles.ts` +- `libs/chat/src/lib/styles/chat-history-search-palette.styles.ts` + +**Deleted:** +- `libs/chat/src/lib/compositions/chat-thread-drawer/` (entire directory, including its `.spec.ts`) + +**Modified:** +- `libs/chat/src/public-api.ts` — add new exports, remove drawer export +- `libs/chat/src/lib/styles/chat-tokens.ts` — add three width tokens to `SPACING_TOKENS` +- `examples/chat/angular/src/...` — migrate usages of `ChatThreadDrawerComponent` to `ChatSidenavComponent`; render `ChatHistorySearchPaletteComponent` in the shell; wire default client-side filter +- `apps/website/content/docs/chat/api/api-docs.json` — regenerated to reflect new exports / removed drawer + +## `chat-sidenav` composition + +### Inputs + +- `agent: Agent` (required) — passed through to the inner thread list for thread-row rendering and active-thread highlighting +- `mode: 'expanded' | 'collapsed' | 'drawer'` (default `'expanded'`) +- `open: boolean` (model, default `false`) — only meaningful when `mode === 'drawer'` +- `threads: Thread[] | null` (default `null`) — if `null`, the threads section is suppressed and the consumer is expected to project their own thread rendering via `[sidenavSections]` +- `activeThreadId: string | null` (default `null`) + +`Thread` is the existing type already consumed by `chat-thread-list` (do not redefine). + +### Outputs + +- `(newChat: void)` — new-chat button clicked +- `(threadSelected: string)` — thread id; relayed from inner `chat-thread-list` +- `(searchOpened: void)` — search button clicked OR Cmd/Ctrl+K fired +- `(openChange: boolean)` — drawer-mode open state changed + +### Layout (expanded) + +``` +┌─ chat-sidenav (data-mode="expanded") ──┐ +│ │ branding/logo slot +├────────────────────────────────────────┤ +│ [+ New chat] [🔍 Search] │ built-in actions row +├────────────────────────────────────────┤ +│ │ consumer destinations +├────────────────────────────────────────┤ +│ Recent │ +│ │ built-in, only if threads !== null +├────────────────────────────────────────┤ +│ │ consumer sections (projects/etc.) +├────────────────────────────────────────┤ +│ │ sticky bottom slot +└────────────────────────────────────────┘ +``` + +### `collapsed` mode + +- Same vertical order, ~56 px wide rail. +- Built-in buttons render icon-only (no labels). `aria-label` and `title` provided. +- Consumer-supplied slot content is expected to render compact; the composition sets `[data-mode="collapsed"]` on the host so consumer styles can target it. +- Threads section renders avatar/initial-letter chips of recent threads (clickable). Limit: 5 most recent. Overflow hidden — the only way to see older threads in collapsed mode is to open search or expand. + +### `drawer` mode + +- Overlay variant. Subsumes the deleted `chat-thread-drawer`. +- Scrim (`button` element so it is keyboard-focusable for click-to-close). +- Focus trap while open: tab is confined to elements inside the drawer. +- Esc closes; emits `(openChange, false)`. +- On open, focus moves to the new-chat button. +- On close, focus returns to the invoking element (consumer is responsible for triggering the drawer from a focusable invoker; framework restores focus via storing `document.activeElement` at open-time). +- Full width on viewports `≤ 767 px`; otherwise uses `--ngaf-chat-sidenav-width-drawer`. + +### CSS tokens (added to `SPACING_TOKENS` in `chat-tokens.ts`) + +```css +--ngaf-chat-sidenav-width-expanded: 280px; +--ngaf-chat-sidenav-width-collapsed: 56px; +--ngaf-chat-sidenav-width-drawer: 280px; +``` + +### Keyboard shortcut + +- Lives on `chat-sidenav`'s constructor (NOT a separate global directive). +- `fromEvent(window, 'keydown').pipe(takeUntilDestroyed(destroyRef))`. +- Matches `(e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k'`. +- On match: `e.preventDefault()`; `this.searchOpened.emit()`. +- Suppressed when `e.target` is ``, `