Skip to content

R8a: restore Hide Other Branches across five node kinds - #171

Merged
dovvnloading merged 1 commit into
mainfrom
r8a/hide-other-branches
Jul 29, 2026
Merged

R8a: restore Hide Other Branches across five node kinds#171
dovvnloading merged 1 commit into
mainfrom
r8a/hide-other-branches

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Summary

Restores the "Hide Other Branches" menu action across chat, code, document, thinking and image nodes, replacing a disabled placeholder with a working branch-isolation toggle.

Problem

All five node kinds' menus carried the item disabled, with the tooltip "Branch visibility isn't built yet". The Qt cutover deleted the legacy scene's toggle_branch_visibility (graphlink_scene.py) without porting a successor.

Legacy's own name is misleading: it never removes anything. It dims every node outside the clicked node's branch — its ancestor chain and everything descended from it — down to a low opacity, restoring full opacity on toggle-off. It is a scene-wide toggle: once active, the button reads "Show All Branches" everywhere, and clicking anywhere clears it regardless of which node's menu the second click came from.

Changes

computeDimmedNodeIds (SceneCanvas.tsx) ports the legacy algorithm to this app's uniform (source, target) edge model. Legacy told "chat replied by chat" and "chat owns a code block" apart with different attribute pairs; the modern backend models both as one edge type, so this tells them apart by kind instead — a node's branch anchor is itself if chat-kind, else its nearest parent if that is chat-kind. The active branch is the set of chat nodes reachable by walking chat-to-chat edges only, in both directions; a content node attached to any branch member counts as active via its anchor, not by the walk expanding through it.

Both the ancestor walk and the descendant search are visited-set-guarded — a deliberate hardening legacy's own equivalent walks lack, since SceneDocument.connect() permits creating a cycle with no validation. Dimming is applied via React Flow's per-node style.opacity, the same mechanism this file already uses for the fade-connections feature, at 0.18 — legacy's own constant, ported unchanged.

Dimming is scoped to exactly the five node kinds that carried this menu item in the legacy app. Every other kind, including Conversation (whose own file already documents excluding this feature deliberately), is left untouched.

Process

Built in two stages. The algorithm and all SceneCanvas state were written and hand-verified against 13 graph scenarios — linear chains, sibling forks, deep descendant chains, content nodes on dimmed branches, focusing from a content node itself, orphaned nodes, manually-created cycles — before any other file was touched. Five agents then wired the mechanical menu-button change into the five node views in parallel from one shared contract.

Three independent reviews followed: one devised its own six adversarial graph scenarios blind to the existing test suite and hand-traced them against the implementation before cross-checking against it; one verified cross-file contract correctness; one checked legacy fidelity and test quality. All three came back clean bar one test-coverage gap — a missing menu-closed assertion in one file's active-state test — fixed directly.

That same direct verification pass caught a real lint error outside the reviewed scope: the original self-heal-on-deleted-origin logic called setState synchronously inside a useEffect, the cascading-render anti-pattern react-hooks/set-state-in-effect exists to catch. Replaced with a value derived during render — simpler, and resolves a stale node reference within the same render instead of one render later.

Verification

Check Result
Backend suite 1033 passed (unaffected — no backend changes)
Frontend tsc --noEmit / ESLint / tests / build clean / 0 errors / 841 passed (31 new) / succeeds
Live: real 13-node scene with an actual branch fork confirmed
Live: toggling dims exactly the sibling branch (5 nodes, multiple kinds incl. a thinking node) to opacity 0.18 confirmed
Live: label flips to "Show All Branches" confirmed
Live: toggling off restores all 13 nodes to opacity 1 confirmed
Live: console zero errors

Related

This closes out the four-item deferred-menu-item cleanup started with Key Takeaway/Explainer Note (#169) and Open Document View (#170).

Chat, code, document, thinking and image node menus each carried a
disabled "Hide Other Branches" stub with the tooltip "Branch visibility
isn't built yet" since the Qt cutover deleted the legacy scene's
toggle_branch_visibility (graphlink_scene.py) without a successor.

Legacy's own name is misleading: it never removes anything. It dims every
node outside the clicked node's branch - its ancestor chain and everything
descended from it - down to a low opacity (0.18, legacy's own
BRANCH_DIM_OPACITY, ported unchanged), restoring full opacity on
toggle-off. It is a scene-wide toggle: once active, the button reads "Show
All Branches" everywhere, and clicking anywhere clears it, regardless of
which node's menu the second click came from.

computeDimmedNodeIds (SceneCanvas.tsx) ports the legacy algorithm to this
app's uniform (source, target) edge model. Legacy told "chat replied by
chat" and "chat owns code block" apart with different attribute pairs
(parent_node/children vs. parent_content_node); the modern backend models
both as one edge type, so this tells them apart by kind instead - a
node's branch anchor is itself if chat-kind, else its nearest parent if
that is chat-kind. The active branch is computed by walking chat-to-chat
edges only, in both directions; a content node attached to any branch
member counts as active via its anchor, not by the walk expanding through
it. Both the ancestor walk and the descendant search are visited-set-
guarded - a deliberate hardening legacy's own equivalent walks lack, since
SceneDocument.connect() allows creating a cycle with no validation at all.
Applied via React Flow's per-node style.opacity, the same mechanism this
file already established for the fade-connections feature, rather than
any DOM removal.

Dimming is scoped to exactly the five node kinds that carried this menu
item in the legacy app - every other kind, including Conversation (whose
own file already documents excluding this feature deliberately), is left
untouched.

Built in two stages: the algorithm and all SceneCanvas state were written
and hand-verified directly against 13 graph scenarios (linear chains,
sibling forks, deep descendant chains, content nodes on dimmed branches,
focusing from a content node itself, orphaned nodes, manually-created
cycles) before any other file was touched, then five agents wired the
mechanical menu-button change into the five node views in parallel from
one shared contract. Three independent reviews followed: one devised its
own six adversarial graph scenarios blind to the existing test suite and
hand-traced them against the real implementation before cross-checking;
one verified cross-file contract correctness (interface fields, prop
threading, tsc, full test run); one checked legacy fidelity and test
quality. All three came back clean bar one test-coverage gap (a missing
menu-closed assertion in one file's active-state test), fixed directly.

That same direct verification pass also caught a real lint error the
review agents had no visibility into, since it was introduced after their
scope was fixed: the original self-heal-on-deleted-origin logic used
setState synchronously inside a useEffect, the exact cascading-render
anti-pattern react-hooks/set-state-in-effect exists to catch. Replaced
with a derived value computed during render - simpler, and fixes a stale
node reference within the same render instead of one render later.

Verified independently: full backend suite (1033, unaffected), frontend
typecheck/lint/tests/build (841 tests, 0 lint errors), and a live browser
check against a real 13-node scene with an actual branch fork - clicking
Hide Other Branches dropped exactly the sibling branch's five nodes
(spanning multiple kinds, including a thinking node attached to that
branch) to opacity 0.18, the label flipped to Show All Branches, and
toggling off restored all thirteen nodes to opacity 1, with zero console
errors throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit 669e7e3 into main Jul 29, 2026
2 checks passed
@dovvnloading
dovvnloading deleted the r8a/hide-other-branches branch July 29, 2026 00:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant