feat(ui): implement recursive nested session expansion#478
Conversation
This change enables the UI to display nested sessions within nested sessions
in a foldable display, recursively, up to 10 levels deep.
## What Changed
### Core Data Structure (session-state.ts)
- Redefined `SessionThread` type to support true recursive nesting:
- Old: `{ parent: Session, children: Session[], latestUpdated: number }`
- New: `{ session: Session, children: SessionThread[], depth: number,
hasChildren: boolean, latestUpdated: number }`
- Renamed `expandedSessionParents` signal to `expandedSessions` to reflect
that ANY session with children can now be expanded, not just top-level parents
- Updated `getSessionThreads()` to build a recursive tree structure using
new `buildSessionThreadTree()` and `computeThreadSignature()` helpers
- Updated `getSessionFamily()` to recursively collect ALL descendants, not
just direct children
- Updated `getVisibleSessionIds()` with `collectVisibleSessionIds()` helper
to recursively collect visible session IDs based on expansion state
- Maintained backward compatibility aliases for renamed functions:
`isSessionParentExpanded`, `setSessionParentExpanded`, etc.
### Session State Exports (sessions.ts)
- Updated imports and exports to use the new function names:
`ensureSessionExpanded`, `isSessionExpanded`, `setSessionExpanded`,
`toggleSessionExpanded`
### Session Events (session-events.ts)
- Updated `ensureSessionParentExpanded` → `ensureSessionExpanded`
in auto-expand logic for child sessions that start working
### Permission Modal (permission-approval-modal.tsx)
- Updated import and usage of `ensureSessionParentExpanded` →
`ensureSessionExpanded`
### UI Rendering (session-list.tsx)
- Updated `SessionRow` component to accept `session` object directly
instead of `sessionId`, plus `depth` and `isLastChild` props
- Derived `isChild` from `depth > 0` instead of explicit prop
- Added `depthClass()` for CSS depth-based indentation
- Created new `SessionThreadRow` recursive component that:
- Renders the current session via SessionRow
- If expanded and has children, recursively renders children with
increased depth
- Updated `filteredThreads` with `subtreeHasMatch()` and `filterThreadTree()`
helpers for recursive filtering
- Updated `allMatchingSessionIds` with `collectThreadIds()` helper for
recursive ID collection
- Removed child-specific `Bot` icon - all sessions now use `User` icon
- Updated expander visibility to show for ANY session with children,
regardless of depth
### Styling (session-layout.css)
- Added depth-based CSS classes `.session-item-depth-{1-10}` with:
- Progressive indentation: 2.25rem for depth 1, up to 13.5rem for depth 10
- Tree connector styling via `::before` and `::after` pseudo-elements
- Proper vertical line handling for last-child at each depth level
### Tests (session-state.test.ts)
- Added comprehensive test suite (683 lines) covering:
- `getSessionThreads`: empty sessions, single sessions, single-level
children, multi-level nested children, sorting, hasChildren computation
- `getSessionFamily`: recursive descendant collection
- Expansion state: toggle, explicit set, ensure logic
- `getVisibleSessionIds`: visibility based on expansion state at
multiple levels
## User-Facing Behavior
- Nested sessions can now be collapsed/expanded at any depth level
- A chevron expander appears on any session that has children
- Children are indented based on their nesting depth
- Tree lines connect parent-child relationships visually
- Expanding a parent auto-expands ancestors when selecting a deeply
nested child session
## Edge Cases Handled
- Sessions with no children show no expander
- Last child at each depth level has shortened vertical tree line
- Thread sorting by latestUpdated works correctly with nested updates
- Cache invalidation properly tracks thread changes at all depth levels
## Implementation Notes
- Depth is limited to 10 levels to prevent excessive indentation
- The `hasChildren` flag is computed once during tree building for
performance
- The Session type's `parentId` field already supported arbitrary
nesting - only the UI rendering needed to be updated
|
PR builds are available as GitHub Actions artifacts: https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/26020161634 Artifacts expire in 7 days.
|
|
Hi @detain Thanks for the PR. Thanks |
|
as far as i know, both opencode and this already supported nested recursive sessions just not in the ui ... i dont think ive seen opencode 5 subagents deep but 3 levels for sure all the time and maybe 4 .. through this ui too .. like if you go to the nested agent you can see its calling other subagents ... so at least 3 levels deep.. maybe its a permission thing in opencode, i do tend to have mine a bit open. |
|
Would you be able to give me a working prompt / agent config / opencode config that allows you to do this please. |
|
it it wasn't until an hour ago i figured out how to run the local dev version to test it and it needs some improvements first so its not ready for merge. i was a bit premature it seems... will respond with a fix and additional info about the nested sub-agents, |
|
err i meant |
|
What lets your subagents spawn more subagents is just config — specifically that the task tool is granted to them: |
|
so just set task to allow and they spawn sub-agents recursively .. to utilize them going more than 3 levels deep on their own though the prompt or instructions would probably be needed to give it some direction. |
The previous signature only included direct children's IDs (without their updated times), so live SSE events for grandchildren and deeper descendants would not invalidate the memoized thread tree until a page refresh. Walk the entire descendant subtree and include each node's updated time so any deep change re-renders correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
fixed a cache invalidation bug in the nested expansion where it was not fully walking the descendants on updates so you would have to refresh the screen to see the new members, this is now fixed. |
|
PR builds are available as GitHub Actions artifacts: https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/26057499810 Artifacts expire in 7 days.
|

This change enables the UI to display nested sessions within nested sessions in a foldable display, recursively, up to 10 levels deep.
What Changed
Core Data Structure (session-state.ts)
SessionThreadtype to support true recursive nesting:{ parent: Session, children: Session[], latestUpdated: number }{ session: Session, children: SessionThread[], depth: number, hasChildren: boolean, latestUpdated: number }expandedSessionParentssignal toexpandedSessionsto reflect that ANY session with children can now be expanded, not just top-level parentsgetSessionThreads()to build a recursive tree structure using newbuildSessionThreadTree()andcomputeThreadSignature()helpersgetSessionFamily()to recursively collect ALL descendants, not just direct childrengetVisibleSessionIds()withcollectVisibleSessionIds()helper to recursively collect visible session IDs based on expansion stateisSessionParentExpanded,setSessionParentExpanded, etc.Session State Exports (sessions.ts)
ensureSessionExpanded,isSessionExpanded,setSessionExpanded,toggleSessionExpandedSession Events (session-events.ts)
ensureSessionParentExpanded→ensureSessionExpandedin auto-expand logic for child sessions that start workingPermission Modal (permission-approval-modal.tsx)
ensureSessionParentExpanded→ensureSessionExpandedUI Rendering (session-list.tsx)
SessionRowcomponent to acceptsessionobject directly instead ofsessionId, plusdepthandisLastChildpropsisChildfromdepth > 0instead of explicit propdepthClass()for CSS depth-based indentationSessionThreadRowrecursive component that:filteredThreadswithsubtreeHasMatch()andfilterThreadTree()helpers for recursive filteringallMatchingSessionIdswithcollectThreadIds()helper for recursive ID collectionBoticon - all sessions now useUsericonStyling (session-layout.css)
.session-item-depth-{1-10}with:::beforeand::afterpseudo-elementsTests (session-state.test.ts)
getSessionThreads: empty sessions, single sessions, single-level children, multi-level nested children, sorting, hasChildren computationgetSessionFamily: recursive descendant collectiongetVisibleSessionIds: visibility based on expansion state at multiple levelsUser-Facing Behavior
Edge Cases Handled
Implementation Notes
hasChildrenflag is computed once during tree building for performanceparentIdfield already supported arbitrary nesting - only the UI rendering needed to be updated