Conversation
|
| Filename | Overview |
|---|---|
| static/sessions.js | Large, well-structured change: adds _isForkWithResolvableParent helper, filters nested forks from top-level rows, attaches them to parent _child_sessions with depth-ordered queue, bubbles state (streaming/unread/attention but NOT timestamp), renders fork children with full interactivity (swipe, rename, context menu), and registers them in _sessionVisibleSidebarIds for batch select. |
| static/style.css | Comprehensive fork-row CSS added (swipe, streaming, attention, long-press); missing .session-child-session-fork.selected rule for batch-select visual feedback. |
| tests/test_session_lineage_collapse.py | New Node-subprocess runtime tests verify fork nesting, chain-flattening under root ancestor, timestamp preservation, attention bubbling, and pinned-fork exemption; also adds _isForkWithResolvableParent eval to existing test harnesses to prevent ReferenceError. |
| tests/test_465_session_branching.py | Updates existing test wording to reflect that the fork guard in _sessionLineageKey prevents lineage merging (not nesting); adds structural static-analysis tests for the new nesting path, search expansion, and state indicators. |
| tests/test_session_rename_lifecycle.py | Updated to match refactored _buildSessionRenameStarter helper; adds blur-commits-title assertion. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[_attachChildSessionsToSidebarRows] --> B{Build sessionIdsInList}
B --> C[Filter collapsedRows: remove child_sessions and unpinned resolvable forks]
C --> D[rows = top-level visible rows]
B --> E[attachDepthFor: depth 0=root, 1=fork child, 2=fork-of-fork]
E --> F[attachQueue sorted by depth]
F --> G{for each child}
G --> H{isChildSession OR isForkChild?}
H -- No --> I[skip]
H -- Yes --> J{cross_surface and not fork?}
J -- Yes --> K[orphans]
J -- No --> L{find parentRow via visibleBySid or visibleBySegmentSid}
L -- found --> M[push to _child_sessions, bubbleSidebarState, update visibleBySegmentSid]
L -- not found --> K
M --> N[return rows + orphans]
D --> N
Reviews (11): Last reviewed commit: "fix(sidebar): add attention and drag-hov..." | Re-trigger Greptile
|
Thanks @rodboev — the concept is good and reusing the existing 🔴 Required: forks render twice (duplicate rows)
Fix: compute const sessionIdsInList = new Set((rawSessions||[]).map(s=>s&&s.session_id).filter(Boolean));
const rows = (collapsedRows||[])
.filter(s => !_isChildSession(s) && !_isForkWithResolvableParent(s, sessionIdsInList))
.map(s => ({...s}));Please also add a runtime test asserting a fork with a visible parent appears exactly once (nested), and a fork with an absent parent stays top-level. 🟠 Design question: nested forks lose their per-row actionsThe nested-child render path (static/sessions.js:5173) renders children as plain So there's a design decision to make:
We'd lean toward A (forks staying first-class) but it's your call as the author — let us know which direction you want and we'll re-review. Once the duplicate-render fix + the action decision are in, this should be a clean merge. |
|
Addressed the follow-up regressions in the nested fork path.
Validation:
|
|
Re-reviewed the new commits ( 🔴 Duplicate-render — fixed and verified
const sessionIdsInList=new Set((rawSessions||[]).map(s=>s&&s.session_id).filter(Boolean));
const rows=(collapsedRows||[])
.filter(s=>!_isChildSession(s)&&((s&&s.pinned)||!_isForkWithResolvableParent(s, sessionIdsInList)))
.map(s=>({...s}));The same 🟠 Per-row actions — you went with Option AThe new const readOnlyChild=_isReadOnlySession(child);
if(!readOnlyChild){
// three-dot menu → _openSessionActionMenu(child, menuBtn)
// swipe affordances (archive / trash-2)
installForkChildSwipe(row, child, actions);
}
row._startRename=_buildSessionRenameStarter(child, mainBtn, ...);That's the first-class direction we hoped for: menu, rename, swipe-delete/archive, context menu, plus a live One small thing to confirmIn Test surface looks solid: |
0740f60 to
cebd6e2
Compare
|
Rebase update: I resolved the branch rebase conflicts by keeping the fork-specific nested behavior (forks now attach under parents and keep full session action/swipe affordances) while preserving current master behavior for non-fork child rows. Validation run:
All tests passed, and I force-pushed |
cebd6e2 to
9f90a84
Compare
cc2eaa1 to
5d7f9b2
Compare
5d7f9b2 to
0c2ada9
Compare
|
Picked this up to ship (the nesting UX is maintainer-approved, and the prior two rounds' findings are genuinely resolved — dedup vs top-level, cycle-safe depth walk, full action surface on nested forks, rename lifecycle all verified correct). I rebased it onto current master myself (it was 236 commits behind; net diff applied clean, Must-fix1. Parent rows get false unread/completion state when a nested fork is streaming. ( const isStreaming=_isSessionEffectivelyStreaming(s)||!!s._child_session_streaming;
_rememberRenderedStreamingState(s, isStreaming); // ← records the bubbled child state under the PARENT idThe parent spinner correctly bubbles 2. Nested forks drop out of batch-select. ( One scope confirm (non-blocking)The diff also makes subagent-parent badges reflect child streaming/unread/attention (change beyond the stated "nest forks" goal). Reads like a deliberate improvement — fine to keep, just flagging it's a behavior change to the existing subagent-child class, and finding #1 above is the concrete bug that rides along with it. Everything else is ready — these are localized fixes. Both gates agree the nesting itself is correct (Opus: "safe to ship" modulo the badge-scope note; Codex: SHIP-ONLY-WITH-FIXES on the two above). Full suite green (8894). Ping me when pushed and I'll re-gate + ship. Thanks @rodboev. 🙏 |
…ore fork batch-select (nesquena#3601) Split own-vs-bubbled streaming so _rememberRenderedStreamingState records only the parent's own state — prevents false unread/completed transitions when a nested fork stops streaming. Include expanded writable fork children in _sessionVisibleSidebarIds and render batch-select checkboxes on fork child rows so nested forks participate in select-all and shift-select.
da4ef6b to
1371e53
Compare
…ws (nesquena#3601) setSessionSelected, toggleSessionSelect, selectAllSessions, and deselectAllSessions used .closest('.session-item') which skips .session-child-session-fork rows, toggling 'selected' on the parent instead. Widen to '.session-item,.session-child-session-fork'. Update source-assertion tests to match the ownStreaming rename.
|
Addressed both must-fix items plus an additional call-site issue an adversarial review caught.
Validation:
|
Thinking Path
session-child-sessionsgroup via_expandedChildSessionKeys). Forks are the third relationship class and receive only a branch-indicator icon, no nesting._sessionLineageKey(static/sessions.js:3482) was intentional: PRs fix: keep explicit fork sessions out of compression lineage #2014 and fix: keep explicit forks out of lineage report #2063 added it to keep forks out of compression-lineage collapse when the parent row is absent or is a compression-continuation. Removing it outright would reintroduce that regression._sessionLineageKeyguard (so forks never merge into compression-lineage chains), but extend_attachChildSessionsToSidebarRowsto also route fork sessions that have a resolvable in-list parent through the existingsession-child-sessionsmachinery._isForkWithResolvableParent(s, sessionIdsInList)helper guards the extension: it returns true only whensession_source==='fork'andparent_session_idis present in the current sidebar payload. CLI-imported forks whose parent is not a WebUI session carry_cross_surface_child_session; the orphan path is preserved for those viaif(!isForkChild&&child._cross_surface_child_session)._resolveSessionIdFromSidebarLineageskips both fork and child-session rows in its candidate scan (static/sessions.js:3526). The child-session skip stays (those rows are nested, not selectable top-level rows), but the fork skip must be removed: once a fork is nested, it may be the active session and must be resolvable.static/sessions.js:4216controls sidebar row order. A parent with a recent fork child should not jump to the top of its date bucket; the parent's own timestamp determines its position, not its children's.static/sessions.js:4583and the_expandedChildSessionKeysset atstatic/sessions.js:1844are reused without modification; the only new wire-up is feeding fork children into_child_sessionsarrays during_attachChildSessionsToSidebarRows.tests/test_465_session_branching.py:85-93asserts that theif(s.session_source==='fork') return null;guard is present in_sessionLineageKey; it will fail the moment the guard is touched. The guard remains in place, but the test assertion wording is updated to reflect the conditional intent (prevent lineage merging, not prevent nesting). A new test asserts the subgrouping path.What Changed
static/sessions.js(after line 3477): new_isForkWithResolvableParent(s, sessionIdsInList)helper that returns true when a session is a fork with its parent in the current sidebar payload.static/sessions.js(line 3702 region):_attachChildSessionsToSidebarRowsinner loop extended to include fork children alongside_isChildSessionchildren;_cross_surface_child_sessionorphan path preserved for CLI-imported forks.static/sessions.js(line 3526):_resolveSessionIdFromSidebarLineagecandidate scan no longer skips fork rows wholesale; fork rows nested as children must remain resolvable when active.static/sessions.js(line 4216): flat timestamp sort preserved at session level; parent rows sort by their own timestamp, not their fork children's activity.tests/test_465_session_branching.py(lines 85-93):test_branch_fork_sessions_do_not_collapse_into_parent_lineagereworded to assert the guard prevents compression-lineage merging (not nesting); newtest_branch_fork_sessions_nest_under_parentasserts_isForkWithResolvableParentexists, that_attachChildSessionsToSidebarRowsuses it, and that_resolveSessionIdFromSidebarLineageno longer skips fork rows.tests/test_session_lineage_collapse.py: addedeval(extractFunc('_isForkWithResolvableParent'));to all three Node subprocess test functions that evaluate_attachChildSessionsToSidebarRows, preventingReferenceErroron CI.Why It Matters
Forked sessions created with
/branchappear as flat top-level rows sorted by timestamp, so a branch off an old conversation jumps to the top of the list, detached from the session it came from. After this change, forked sessions with a resolvable parent are nested under that parent (collapsible via the existing child-count badge), keeping the sidebar readable for power users with branch-heavy trees. Closes #3224.Verification
On Windows, add
--noconftestif collection fails due to the pre-existingWinError 1314symlink-privilege issue; the static-analysis tests do not depend on conftest fixtures. The real gate is CI on Linux (Python 3.11, 3.12, 3.13).Manual steps: open the WebUI with at least one forked session (use
/branchfrom an older conversation). Confirm: the fork appears nested under its parent row, not at the top of the flat list; the parent row's date-bucket position did not change; the fork is collapsed by default; clicking the child-count badge expands it; opening the fork session resolves correctly. Also confirm CLI-imported forks (without a WebUI parent) still appear as flat orphan rows and are not silently dropped.Risks / Follow-ups
if(s.session_source==='fork') return null;guard in_sessionLineageKeyis deliberately preserved; removing it would allow a fork whose parent is a compression-continuation row to merge into the lineage chain, reintroducing the regression fixed by PRs fix: keep explicit fork sessions out of compression lineage #2014 and fix: keep explicit forks out of lineage report #2063._cross_surface_child_session) are not affected: the orphan path for true subagent children is preserved via the!isForkChild&&child._cross_surface_child_sessioncondition.created_atfield from the backend; left as a follow-up.session-branch-indicatoricon added by feat: session branching (/branch) — fork conversation from any point #465 remains on both the parent row and on the fork child row inside the expanded group; this is consistent with the existing subagent-child rendering pattern.Model Used
Claude Opus 4.8 via Claude Code CLI