feat: root-as-head stack rendering with tree expansion#70
Draft
comp615 wants to merge 7 commits intoblock:mainfrom
Draft
feat: root-as-head stack rendering with tree expansion#70comp615 wants to merge 7 commits intoblock:mainfrom
comp615 wants to merge 7 commits intoblock:mainfrom
Conversation
- Thread.handoffChildIds: string[] replaces singular handoffChildId (legacy field kept for backward compat, set to last child seen) - ThreadChain.descendantsTree: recursive ThreadChainNode[] for branching (flat descendants[] kept for UI compat) - ThreadStack.topology: adjacency maps (rootId, childToParent, parentToChildren) - Fix threadChain.ts: was reading thread.relationships which getThreads() never populates; now uses handoffParentId/handoffChildIds - All new fields are optional/additive; no UI changes needed Amp-Thread-ID: https://ampcode.com/threads/T-019c869d-7987-71df-99a5-e64a57114bee Co-authored-by: Amp <amp@ampcode.com>
- threadStacks.test.ts: assert topology adjacency maps on 'multiple children' and 'fan-out' tests (rootId, parentToChildren forks, childToParent edges) - threadChain.test.ts: new test file with 6 tests covering linear chain, fork descendants tree, mid-chain queries, unknown thread, and flat-vs-tree consistency Amp-Thread-ID: https://ampcode.com/threads/T-019c869d-7987-71df-99a5-e64a57114bee Co-authored-by: Amp <amp@ampcode.com>
- Remove ThreadChain.descendants (flat ChainThread[]) - Make ThreadChain.descendantsTree required (was optional) - Drop flattenTree() from threadChain.ts (no longer needed) - Update ThreadChainContent.tsx: recursive DescendantNodes renderer with depth-based indentation and GitFork icon for branches - Update useThreadDiscovery.ts: chainCount uses descendantsTree.length - Update tests to assert on tree structure only Amp-Thread-ID: https://ampcode.com/threads/T-019c869d-7987-71df-99a5-e64a57114bee Co-authored-by: Amp <amp@ampcode.com>
The plural handoffChildIds (added in PR block#59) fully replaces it. Removed from Thread interface and threadCrud assignment. Amp-Thread-ID: https://ampcode.com/threads/T-019c8702-28e9-73ba-b5e4-496c1dc11651 Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c8b80-a1a7-7121-901f-3fcb77e68845 Co-authored-by: Amp <amp@ampcode.com>
- buildThreadStacks() now uses root (no parent) as head instead of most-recent - Added lastActiveDate to ThreadStack for sort ordering - Added descendants field (DFS tree-ordered) alongside deprecated ancestors - Re-sort entries by lastActiveDate so active stacks float to top - New StackTree component renders expanded stacks as indented tree via topology - ThreadRow supports stackDepth prop for depth-based indentation - Stack head Updated column shows most recently active thread's time - KanbanView uses getLastActiveThread() for column placement and time display - Removed mutable visited Set from StackTree render (React strict mode fix) - All three views (table, kanban, cards) use tree rendering with depth - 258 tests pass, typecheck + lint clean Amp-Thread-ID: https://ampcode.com/threads/T-019c8b24-9f53-7038-a667-56ab9bee122f Co-authored-by: Amp <amp@ampcode.com>
…ated ancestors alias - Extract duplicated graph-building logic (threadMap, childToParent, parentToChildren) from threadChain.ts and threadStacks.ts into a shared buildHandoffGraph() helper in shared/utils.ts - Remove deprecated 'ancestors' field from ThreadStack type (was an alias for 'descendants') Amp-Thread-ID: https://ampcode.com/threads/T-019c8b82-ed31-7099-b07a-2fc28341ad52 Co-authored-by: Amp <amp@ampcode.com>
jom-sq
approved these changes
Feb 23, 2026
Collaborator
jom-sq
left a comment
There was a problem hiding this comment.
i technically put the newest one on top on purpose at the time but yeah im fine with this!
Contributor
Author
@jom-sq yeah this one I feel the most awkward / unsure about. I think it both makes sense to have the newest on top, but also to see the tree to follow the thread as it were...maybe we chat live on this I do not feel as strongly about this one since the view vs sort is quirky |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR description was generated by Amp.
Problem
The stack toggle in the main thread list groups handoff-related threads but has three issues:
root → [branch-a → leaf-a, branch-b], expansion shows a flat interleaved list with no indication of which branch a thread belongs toBefore (flat list)
After (tree)
Screenshot (after): 9-thread stack expanded as an indented tree with depth-based connector lines:
Solution
Commits follow dependency order:
feat: root-as-head stack rendering with tree expansion— Core behavior change: root thread is now the stack head,lastActiveDatedrives sort position, expanded stacks render as indented trees viaStackTree.tsx, DFS-ordereddescendantsfield replacesancestorsrefactor: extract buildHandoffGraph to shared/utils and remove deprecated ancestors alias— Deduplicates the handoff graph-building logic (threadMap + parentToChildren + childToParent) intobuildHandoffGraph()inshared/utils.ts, used by boththreadStacks.ts(client) andthreadChain.ts(server). Removes the deprecatedancestorsalias fromThreadStack.Key changes
Data model (
shared/types.ts):ThreadStack.ancestors→ThreadStack.descendants(tree-ordered via DFS)ThreadStack.lastActiveDate(most recentlastUpdatedDateacross all members)Shared utility (
shared/utils.ts):buildHandoffGraph(threads)→{ threadMap, childToParent, parentToChildren }— extracted from duplicated code inthreadStacks.tsandthreadChain.tsStack building (
src/utils/threadStacks.ts):descendantsbuilt via DFS from root's children, sorted bylastUpdatedDatedesc at each levellastActiveDatedesc so active stacks float to the topgetLastActiveThread()helper for kanban column placementTree rendering (
src/components/ThreadList/StackTree.tsx):topology.parentToChildrenfrom rootThreadRowwithstackDepthprop for indentationlastUpdatedDatedesc for deterministic orderingThreadRow (
src/components/ThreadList/ThreadRow.tsx,types.ts):stackDepthprop for depth-based indentation (marginLeft: (depth-1) * 16px)displayLastUpdatedprop so the head row showslastActiveDate(most recent activity) rather than the root's own last updateKanban + Detail Card views: Updated to use
descendants,topology, andgetLastActiveThread()for correct column placement and tree-structured expansion.Server (
server/lib/threadChain.ts): Now usesbuildHandoffGraph()from shared utils instead of inline graph construction.React Strict Mode Fix
During development, discovered a bug where mutable
Setobjects were passed as props and mutated during render inStackTree. React strict mode double-renders components, so the second render pass would see the already-mutated Set and skip all nodes. Fix: create newSetinstances instead of mutating shared state.Dependencies
Depends on PR #59 (
ccroom/fork-topology-data-model) which introducedThreadStackTopologyand fork-aware chain traversal.Sibling PR: PR #69 (
ccroom/full-tree-chain-display) — full-tree rendering in the chain discovery panel.Verification
pnpm typecheckpasses (0 errors)pnpm test)pnpm lintcleanAI Tool Validation
Copy and paste this prompt into your AI tool (Amp, Cursor, etc.):