fix(canvas): toggleFold handles depth-default-folded nodes (#1439)

toggleFold previously only mutated urlFoldedSet, which had no effect
when the clicked node was folded BY THE DEPTH DEFAULT (not by an
explicit URL override). Result: at ?depth=1 where both groups are
folded by depth-default, double-clicking bootstrap-kit (after #1438's
dblclick-on-group → toggleFold branch) was a no-op — the urlFoldedSet
delete didn't change the composed foldedSet, the canvas didn't budge.

New behaviour:
  - If clicked node is folded by ANY source: switch to depth=all AND
    explicitly fold every OTHER previously-folded group. Only the
    clicked group ends up visibly unfolded — exactly the operator-
    requested "expand only the respective parent" UX.
  - If clicked node is unfolded: add to urlFoldedSet to fold it
    without changing depth.

Caught via Playwright after #1438 landed and dblclick still didn't
unfold the clicked group at ?depth=1.

Co-authored-by: e3mrah <1234567+e3mrah@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
e3mrah 2026-05-12 12:39:58 +04:00 committed by GitHub
parent 24a2b13870
commit bb1bff245a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -263,12 +263,42 @@ export function FlowPage({
const toggleFold = useCallback(
(nodeId: string) => {
if (!adapter.groupIds.has(nodeId)) return
const next = new Set(urlFoldedSet)
// foldedSet is the COMPOSED set: depth-default-folded urlFoldedSet.
// The previous implementation only mutated urlFoldedSet, which had
// no effect when the node was folded by the depth default — the
// composed set stayed the same and the canvas didn't budge. The
// operator-reported "double-click on a parent bubble it is
// expanding all the parent instead of expanding only the respective
// parent" was the consequence: the dblclick was firing on a
// default-folded node, so toggleFold's delete on urlFoldedSet was
// a no-op AND the dblclick handler used to instead navigate to
// /jobs/<group>, dropping the ?depth= filter and re-rendering
// with everything elided.
//
// New behaviour:
// - If the node is currently folded (by ANY source), unfold it:
// switch to depth=all and explicitly fold every OTHER group
// that was previously folded. Result: only this one group is
// visibly unfolded; other groups stay collapsed.
// - If the node is currently unfolded, add it to urlFoldedSet
// to collapse it without changing the depth.
const isFolded = foldedSet.has(nodeId)
if (isFolded) next.delete(nodeId)
else next.add(nodeId)
const arr = [...next].filter(Boolean)
setSearchPatch({ folded: arr.length > 0 ? arr.join(',') : undefined })
if (isFolded) {
const others = new Set<string>(foldedSet)
others.delete(nodeId)
const arr = [...others].filter(Boolean)
setSearchPatch({
depth: 'all',
folded: arr.length > 0 ? arr.join(',') : undefined,
})
} else {
const next = new Set(urlFoldedSet)
next.add(nodeId)
const arr = [...next].filter(Boolean)
setSearchPatch({
folded: arr.length > 0 ? arr.join(',') : undefined,
})
}
},
[adapter.groupIds, foldedSet, urlFoldedSet, setSearchPatch],
)