← all docs · north-os21 July 2026

Chat detail level — implementation plan

Status: Planned
Author: Christophe
Date: 2026-07-21
Repo: north-os
Related: docs/superpowers/specs/2026-07-21-chat-detail-level-design.html

Shape

Two independent workstreams over a shared interface seam, integrated on branch chat-detail-level, merged to main. The invariant (presentation-only; the level never crosses the API boundary) is enforced by scope: no workstream touches packages/chat-runtime, packages/agent-runtime, or any prompt/configurable code. A diff touching those packages is a review-rejection by itself.

Phase 0 — seam (orchestrator, this branch)

  1. Commit spec + this plan; register status plannedin-progress via bun docs/superpowers/status.ts set chat-detail-level in-progress in the implementation-start commit.
  2. Commit the interface seam only: export type ChatDetailLevel = 0 | 1 | 2 | 3 | 4 + optional detailLevel on ChatEnvironment (+ export from the package index). No behavior. This keeps both worker branches independently typecheck-green.

Workstream A — storage, API, settings UI, host wiring

Branch feat/chat-detail-level-settings. Sole DB toucher — the only branch that runs bun db:generate.

  1. Schema: new domain file packages/db/src/schema/user_chat_preference.ts per the spec (user_id PK → user cascade, chat_detail_level int CHECK 0–4, updated_at), exported from the schema index. Pattern: matter_hidden.
  2. Migration: bun db:generate, review SQL, fix schema TS and regenerate if wrong (never patch SQL). Check the journal when watermark (PR #5 rebaseline) and bump metadata if the new entry sorts below the DB max.
  3. GRANT: SELECT, INSERT, UPDATE to app_role in packages/db/scripts/provision-roles.ts. Never in the migration.
  4. API: GET/PATCH /api/settings/chat, Hono + apiAuth + Zod, in a new small preferences router mounted alongside the existing routers in apps/web/lib/api/app.ts (do not bolt onto the media-specific settingsMediaRouter). Missing row reads as level 1; PATCH upserts. Route tests per the existing router-test pattern.
  5. Settings UI: new "Chat detail" section on Settings → Appearance; five-stop control (shadcn primitives, semantic tokens only, DESIGN.md radius/border rules), stop labels + one-line descriptions from the spec table. The Appearance page is client-only: the control fetches GET /api/settings/chat on mount to seed the selection (skeleton until resolved), optimistic PATCH on change.
  6. No per-page chat wiring. Level resolution for the chat surface is Workstream B's chat-ui hook (env override → env.fetch GET → default 1), which covers every LiveChat mount site (deal chats, task chats, automations board, skill editor, same-origin Word pane) automatically. Workstream A owns only the API + settings control.

Workstream B — chat-ui surfacing

Branch feat/chat-detail-level-ui. Touches packages/chat-ui only.

  1. Level resolution hook (spec "Plumbing"): resolved level = env.detailLevel if supplied, else one GET /api/settings/chat via env.fetch/apiUrl on mount, else 1 until resolved. Serves every host with zero per-page wiring. Level 1 must be a byte-for-byte no-op against today's rendering — the existing test suite is the regression net.
  2. Level 0 — leaf suppression, never groupBy (groupBy-null renders parts inline — that is the visible path). In the GroupedParts render callback return null for group-reasoning, the activity group, internal tool-call parts, and working-note text parts. Tests assert absence from the DOM.
  3. Level 4 — empty note membership at the source: compute workingNoteParts/housekeepingNoteParts as empty (memoized on parts + level) so groupBy, the muted-styling text branch, the live line, and housekeeping re-homing all fall through to full answer prose. Tools stay grouped (pre-e35aae81 fidelity).
  4. Group open state: add a defaultOpen prop to ToolGroupBlockToolGroupRoot (none is passed today); set at levels 2–4. Individual tool cards keep their own open/closed defaults at every level.
  5. Reasoning: gate the group-reasoning case in thread.tsx (not reasoning.tsx — its group impl is deprecated/unused): defaultOpen = running || level >= 3 (levels 1–2 keep today's open-while-streaming); null at level 0.
  6. Level-0 streaming: net-new hoisted live line in the assistant-message renderer (the existing one lives inside the hidden ToolGroupBlock): latest working note else neutral shimmer while running; make ThinkingIndicator level-0-aware (no doubling, no empty message on reasoning/tool-only turns); nothing after completion.
  7. User-facing tool cards (isUserFacingToolPart, MCP app cards) and the inline housekeeping chip (levels 0–3, DOM position unchanged from today) stay untouched by the gate.
  8. Tests (spec test plan items 1–4 and 6): placement-table test across all five levels on one fixture (level 0 asserts absence; level 4 asserts answer-prose slot); request-path byte-identity guard exercising prepareSendMessagesRequest at levels 0 vs 4; convertMessage level-independence guard; level-0 + level-1 streaming; selector referential stability.

Integration (orchestrator)

  1. Fable-review each worker branch independently; findings route back to the worker. No merge with open findings.
  2. Merge B, then A, into chat-detail-level; the workstreams share no files (A: db + apps/web; B: chat-ui), the only coupling is B's hook calling A's route path.
  3. Run bun run lint + bun run typecheck from root once, plus chat-ui and touched-package tests. bun run eval:gate is not required — no retrieval/routing/memory surface is touched. Invariant verification: git diff --stat main -- packages/agent-runtime packages/chat-runtime must be empty, AND apps/web/lib/chat + the chat request path in live-chat.tsx must contain no detailLevel/user_chat_preference reference (the diff guard alone would not catch a leak there — the byte-identity test is the load-bearing guard).
  4. Rebase-merge to main; flip status to implemented in the same push. Dev-stack restart via the deploy bot; verify on dev: settings control round-trips, one chat turn rendered at level 0 and level 4. No preprod deploy — Christophe's call.

Risks