Skip to content

fix: hide playground sidebar subtrees when an ancestor folder is collapsed - #6278

Open
prql-bot wants to merge 1 commit into
mainfrom
fix/playground-sidebar-nested-collapse
Open

fix: hide playground sidebar subtrees when an ancestor folder is collapsed#6278
prql-bot wants to merge 1 commit into
mainfrom
fix/playground-sidebar-nested-collapse

Conversation

@prql-bot

@prql-bot prql-bot commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

The playground sidebar decided whether to draw a row by testing only its immediate parent (parent == null || openFolders[parent] || depth === 0), so collapsing a folder hid its children but left its grandchildren on screen — they float up to the top of the sidebar, indented but with no visible parent. This walks the ancestor chain instead: a row renders only if every folder above it is open, which is what the disclosure triangle already implies.

Found during the nightly survey of web/playground/src/sidebar/Sidebar.jsx.

Reproducing it

Open project in the sidebar, open target.md inside it, then collapse project again. The four snippet rows from target.md stay visible.

Against the real generated book.json (210 entries, max depth 4), replaying the old and new predicates over the same openFolders state — {"project/target.md": true}, i.e. project collapsed while the child folder is still flagged open:

orphans before fix: 4  after fix: 0

The orphans are project/target.md_examples.prql, project/target.md_examples_2.prql, project/target.md_version.prql, project/target.md_version_2.prql.

The same replay confirms the states that already worked are unchanged: with nothing open only the 3 depth-0 rows render, and with both project and project/target.md open all 4 snippet rows render — including after reopening project, so the child folder's open state survives an ancestor collapse rather than being reset.

The childrenVisible accumulator relies on parents being emitted before their children. That holds by construction: getAllFiles yields a directory and then recurses into it, and generateBook.cjs writes entries in that order.

Two smaller things in the same rendering loop, both previously masked:

  • marginLeft: ${12 * depth}px produced NaNpx for the examples, tables and local storage sections, whose entries are [editor, content] with no tree metadata. Browsers drop the invalid value, so it looked right; it's now depth ?? 0.
  • toggleFolder mutated the openFolders state object in place before spreading it into setOpenFolders. Replaced with the functional updater, which is the same behavior without the mutation.

The wrapping React.Fragment went away with the inline conditional — the row is now skipped with continue — which also drops the React import this file no longer needs, and lets the key be the filename rather than a positional index.

Verification

No regression test: the playground has no test runner. @testing-library/react and @testing-library/jest-dom are in dependencies, but there is no test script in web/playground/package.json and no test files anywhere under web/playground/, so a component test would mean introducing a runner and wiring it into CI — worth doing, but as its own change rather than folded in here. The behavior was instead verified by replaying both predicates over the real book.json as above.

esbuild --loader:.jsx=jsx --jsx=automatic web/playground/src/sidebar/Sidebar.jsx  # transforms clean
prettier --check web/playground/src/sidebar/Sidebar.jsx                          # clean

build-web runs on this PR (the web path filter in tests.yaml matches web/**) and builds the playground, so the Vite build is covered there.

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