feat(web): support deeply nested chat sessions with context anchors#759
Conversation
Replace single-level groupSessions with recursive buildSessionTree that:
- Supports arbitrary depth via task.parentTaskId chain
- Lifts stale/stopped ancestors from allSessions as dimmed "context anchors"
so deeply nested descendants remain visible when their parents are stopped
- Detects ancestry cycles (both in walk-up order assignment and parent
attachment) to prevent infinite loops from pathological task graphs
- Caps visible indent at MAX_VISUAL_DEPTH=5 with L{N+1} overflow badge
- Preserves green rail visual language from TaskGroup
SessionList now takes optional allSessions prop so the tree can resolve
ancestors across stale/recent buckets. Desktop sidebar and MobileSessionDrawer
both pass allSessions through.
Removed legacy TaskGroup / groupSessions path (replaced by SessionTreeItem).
Tests: 18 cases covering depth, anchors, siblings, ordering, aggregates,
cycles, and search.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UI/UX specialist found a critical `??` nullish coalescing bug that kept all collapsible session-tree nodes collapsed on first render: since `hasMatchingDescendant` resolves to `false` (not `undefined`) when no search is active, the ?? chain short-circuited there and never reached `node.isContextAnchor` or the depth-0 fallback. Replaced with a proper boolean cascade (`defaultExpanded !== undefined` check + `||` chain) so top-level parents and context anchors correctly auto-expand. Accessibility fixes: - Move context-anchor `aria-label` from a non-focusable div onto the `SessionItem` select button via a new `ariaLabel` prop, so screen readers announce "stopped ancestor" when navigating. - Replace `opacity: 0.55` dimming (3.33:1 contrast, fails WCAG AA) with a distinct slate-tone Context badge at 11px bold that meets 4.5:1. - Widen expand/collapse button from 22px to 44px to meet WCAG 2.5.5 touch-target minimum on mobile. - Reword "context only (stale)" tooltip to "Stopped ancestor — click to open" to match actual interactive behavior. - Gate hover bg-override on selected state so it no longer erases the bg-inset background when hovering the active row. - Consolidate SUB/NESTED badge variant into a single neutral "N sub" label to remove jargon the implementation distinction couldn't justify. Test-engineer found missing behavioral tests for interactive elements (rule 02). Added `apps/web/tests/unit/SessionTreeItem.test.tsx` with 19 cases: C1 regression (initialExpanded falls through full chain), expand/collapse toggle (3a), search-driven auto-expand (3b), user-toggle overrides search (3c), depth overflow badge (3d), context-anchor aria label, onSelect wire-up (5), hover/selected interaction (M3), and the descendant-count badge label. Extended sessionTree.test.ts with anchor-root ordering by first visible descendant (1a) and self-referential cycle safety (6a). 1827 tests pass (up from 1808). Lint 0 errors, typecheck clean, build ok. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds apps/web/tests/playwright/nested-session-sidebar-audit.spec.ts covering three scenarios (normal 2-level nesting, deeply nested L6+, stopped-ancestor context anchors) across iPhone SE (375px), iPhone 14 (390px), and Desktop (1280x800). Asserts no horizontal overflow and that session topics render. Also files tasks/backlog/2026-04-18-staging-migration-v8-corruption.md documenting the pre-existing staging D1 v8 migration corruption that blocks all staging deploys (unrelated infra state issue). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88920e7 to
aa763e3
Compare
Finalization update (2026-04-19)Rebased on latest
Staging deploy blocker (pre-existing, unrelated to this PR)Triggered This resource ( This is pre-existing staging state drift, not caused by this PR. It affects ALL non-trial branches attempting to deploy to staging. Per Verification evidence
Why removing
|
|


Summary
Fixes a bug where deeply-nested chat sessions (child-of-child, etc.) never appeared in the project chat sidebar when all ancestors were in the
stoppedstate. Session topics with any non-trivial lineage were effectively invisible.apps/web/src/pages/project-chat/sessionTree.ts) that joins the filtered recent-sessions list with the full all-sessions pool to reconstruct lineage for deep children. Stopped ancestors of a visible deep child are surfaced as dimmed "context anchors" so navigation is preserved without reintroducing noise.SessionTreeItemcomponent that renders nodes recursively with a green-rail visual language (matchingTaskGroup), caps visible indent at ~4 levels, and shows anL6+badge beyond the cap to keep mobile layouts clean.Validation
pnpm lintpnpm typecheckpnpm test— 1808 tests pass (including 18 newsessionTree.test.tscases and new behavioralSessionTreeItem.test.tsxtests)Staging Verification (REQUIRED for all code changes — merge-blocking)
tasks/backlog/2026-04-18-staging-migration-v8-corruption.md.N/A: no infra changes(frontend-only; touchesapps/web/src/pages/project-chat/and tests)Staging Verification Evidence
Staging cannot be verified because the
Deploy Stagingworkflow has been failing on an orphanedv8entry in the staging D1d1_migrationstable, regardless of PR contents. This PR does not touch any migration, infra, or D1 code — it is purely client-side UI inapps/web/src/pages/project-chat/.As a fallback, I ran a comprehensive local Playwright visual audit against the Vite preview build:
apps/web/tests/playwright/nested-session-sidebar-audit.spec.tsdocumentElement.scrollWidth <= window.innerWidth), crash-guard (no error-boundary), and the expected session topics are present in the DOM.codex/tmp/playwright-screenshots/nested-sidebar-*The
needs-human-reviewlabel is applied per rule 25 because staging verification cannot complete end-to-end.UI Compliance Checklist
L6+badge replaces deep indents to prevent horizontal overflowaria-labels added on tree rows (reviewer C2 fix); touch targets ≥ 44px (reviewer H1 fix: expand chevron bumped to 22px square); contrast adjustments (reviewer H2 fix)TaskGrouppattern.codex/tmp/playwright-screenshots/End-to-End Verification
Data Flow Trace
API returns sessions + tasks
→
listChatSessions()(apps/web/src/lib/api/sessions.ts)→
listProjectTasks()(apps/web/src/lib/api/tasks.ts)Project chat state merges both pools
→
useProjectChatState.tsstoressessions(visible) +allSessions(full pool with stopped ancestors)Tree model reconstructs lineage
→
sessionTree.ts:buildSessionTree(visible, all, tasks)→ Walks parent chains, marks stopped ancestors as
isContextAnchor, enforces cycle-safe seen-set, emitsSessionTreeNode[]Sidebar renders recursively
→
SessionList.tsxiterates roots→
SessionTreeItem.tsxrecurses children with depth cap + dim-anchor stylingClick on any tree row selects the session
→
SessionTreeItem.onSelect(sessionId)→ existing selection handler inuseProjectChatStateUntested Gaps
N/A: full tree-build flow covered by 18 unit tests (sessionTree.test.ts) + behavioral render tests (SessionTreeItem.test.tsx) + 15 Playwright visual tests. Staging verification deferred — see above.Post-Mortem
N/A: not a classical bug fix — this is a missing-feature fix (deep nesting was never supported). No regression-class post-mortem applies.Specialist Review Evidence
needs-human-reviewlabel added — staging deployment cannot complete due to pre-existing infra corruption; human must decide whether to proceed without full staging verification.tests/unit/SessionTreeItem.test.tsxcovering expand/collapse (3a), search auto-expand (3b), user-toggle override (3c), depth L6+ badge (3d), onSelect wire-up (5), and C1 regression.sessionTree.test.tsextended with anchor root ordering (1a) and self-referential cycle (6a).Exceptions
app.sammy.partytasks/backlog/2026-04-18-staging-migration-v8-corruption.mdis resolved, this branch should be re-deployed and re-verified before merge.Agent Preflight
Classification
External References
N/A: no external API surface touched. Prior art research (UI/UX for deeply nested trees) informed the design — considered VS Code Explorer (expand/collapse + indent guides), Notion page tree (context anchor pattern for stale parents), and file-manager tree views. Picked theL6+depth-cap badge as a mobile-friendly hybrid.Codebase Impact Analysis
apps/web/src/pages/project-chat/sessionTree.ts(new)apps/web/src/pages/project-chat/SessionTreeItem.tsx(new)apps/web/src/pages/project-chat/SessionList.tsx(modified — uses tree model)apps/web/src/pages/project-chat/useProjectChatState.ts(modified — exposesallSessions)apps/web/tests/unit/sessionTree.test.ts(new — 18 cases)apps/web/tests/unit/SessionTreeItem.test.tsx(new)apps/web/tests/playwright/nested-session-sidebar-audit.spec.ts(new)tasks/active/2026-04-18-nested-chat-sidebar-tree.md(active task file)tasks/backlog/2026-04-18-staging-migration-v8-corruption.md(new — tracks staging blocker)Documentation & Specs
N/A: purely client-side UX refinement of an existing surface. No user-facing docs or specs describe the sidebar's nesting behavior (it did not previously support deep nesting).Constitution & Risk Check
MAX_VISIBLE_DEPTH = 4) andL6+threshold are defined as named constants at the top ofSessionTreeItem.tsx— not magic numbers inline.buildSessionTree— addressed with seen-set cycle guards on both root-walk and parent-attachment paths; covered bysessionTree.test.ts6a (self-referential cycle) and 6b (longer cycle).