feat(tree): Expand all / Collapse all for the space page tree #8

Closed
Ghost wants to merge 2 commits from feat/tree-expand-collapse-all into develop

Implements docs/backlog/tree-expand-collapse-all.md.

What

Server-authoritative whole-tree fetch + sidebar menu commands, so a deep space tree expands in one request instead of a per-level BFS request storm.

Server

  • POST /pages/tree (SidebarPageTreeDto: spaceId | pageId), same CASL space scoping as /sidebar-pages. Returns the whole space tree / subtree as a flat list in the sidebar item shape, ordered by position (collate-C byte order); content never fetched.
  • getSidebarPagesTree reproduces getSidebarPages' two-branch permission model: open space → spaceCanEdit; restricted space → seed the full descendant set, then prune via filterAccessibleTreePages + filterAccessiblePageIdsWithPermissions (keeps restricted-but-granted pages, prunes inaccessible subtrees). hasChildren derived from the final filtered set, so it can't reveal inaccessible children.
  • page.repo.getSpaceDescendants: recursive CTE seeded by space roots.

Client

  • SpaceTree is forwardRef exposing expandAll/collapseAll/isExpanding; expandAll fetches once, replaces current-space nodes, opens every branch (current space only), aborts on space switch, surfaces real errors; collapseAll only touches current-space ids (the open-map is shared across spaces).
  • SpaceMenu gains Expand all / Collapse all (no admin gate — view operation).

Review & fixes

A review caught that the first cut used the *ExcludingRestricted CTE, which would have hidden restricted-but-granted pages (divergent from the sidebar, not a leak). Fixed to seed the full set + filterAccessibleTreePages. Adversarial permission review otherwise confirmed parity (controller CASL, hasChildren, canEdit, no content, cross-workspace access blocked).

Verification

  • pnpm --filter server build / pnpm --filter client build clean; 7 unit tests pass (sidebar-pages-tree.spec.ts).
  • Browser (headless Chromium): seeded a 3-level tree, Expand all issued exactly one POST /api/pages/tree (zero /sidebar-pages storm) and revealed all levels incl. the grandchild; Collapse all collapsed back to roots. No app errors. Screenshots captured.

🤖 Generated with Claude Code

Implements `docs/backlog/tree-expand-collapse-all.md`. ## What Server-authoritative whole-tree fetch + sidebar menu commands, so a deep space tree expands in **one request** instead of a per-level BFS request storm. **Server** - `POST /pages/tree` (`SidebarPageTreeDto`: `spaceId | pageId`), same CASL space scoping as `/sidebar-pages`. Returns the whole space tree / subtree as a flat list in the sidebar item shape, ordered by `position` (collate-C byte order); content never fetched. - `getSidebarPagesTree` reproduces `getSidebarPages`' two-branch permission model: open space → `spaceCanEdit`; restricted space → seed the full descendant set, then prune via `filterAccessibleTreePages` + `filterAccessiblePageIdsWithPermissions` (keeps restricted-but-granted pages, prunes inaccessible subtrees). `hasChildren` derived from the final filtered set, so it can't reveal inaccessible children. - `page.repo.getSpaceDescendants`: recursive CTE seeded by space roots. **Client** - `SpaceTree` is `forwardRef` exposing `expandAll/collapseAll/isExpanding`; expandAll fetches once, replaces current-space nodes, opens every branch (current space only), aborts on space switch, surfaces real errors; collapseAll only touches current-space ids (the open-map is shared across spaces). - `SpaceMenu` gains **Expand all** / **Collapse all** (no admin gate — view operation). ## Review & fixes A review caught that the first cut used the `*ExcludingRestricted` CTE, which would have hidden restricted-but-granted pages (divergent from the sidebar, not a leak). Fixed to seed the full set + `filterAccessibleTreePages`. Adversarial permission review otherwise confirmed parity (controller CASL, `hasChildren`, `canEdit`, no content, cross-workspace access blocked). ## Verification - `pnpm --filter server build` / `pnpm --filter client build` clean; 7 unit tests pass (`sidebar-pages-tree.spec.ts`). - Browser (headless Chromium): seeded a 3-level tree, **Expand all** issued exactly one `POST /api/pages/tree` (zero `/sidebar-pages` storm) and revealed all levels incl. the grandchild; **Collapse all** collapsed back to roots. No app errors. Screenshots captured. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 2 commits 2026-06-20 05:32:01 +03:00
Adds a server-authoritative whole-tree endpoint and sidebar menu commands
so a deep space tree can be expanded in one request instead of a per-level
BFS storm.

Server:
- POST /pages/tree (SidebarPageTreeDto: spaceId | pageId), same CASL space
  scoping as /sidebar-pages. Returns the whole space tree / subtree as a flat
  list in the sidebar item shape (id, slugId, title, icon, position,
  parentPageId, spaceId, hasChildren, canEdit), ordered by position
  (collate C byte order), content never fetched.
- page.service.getSidebarPagesTree reproduces getSidebarPages' two-branch
  permission model: open space -> spaceCanEdit; restricted space -> seed the
  full descendant set then prune via filterAccessibleTreePages +
  filterAccessiblePageIdsWithPermissions (keeps restricted-but-granted pages,
  prunes inaccessible subtrees). hasChildren is derived from the final
  filtered set so it can never reveal inaccessible children.
- page.repo.getSpaceDescendants: recursive CTE seeded by space roots.

Client:
- SpaceTree is forwardRef exposing expandAll/collapseAll/isExpanding;
  expandAll fetches the whole tree once, replaces current-space nodes, opens
  every branch (current space only), aborts on space switch, surfaces real
  errors; collapseAll collapses only current-space ids (shared open-map).
- SpaceMenu gains Expand all / Collapse all items (no admin gate).

Implements docs/backlog/tree-expand-collapse-all.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ghost force-pushed feat/tree-expand-collapse-all from b38b71eb51 to 0c7d67fe2a 2026-06-20 14:45:08 +03:00 Compare
Ghost added 1 commit 2026-06-20 14:45:22 +03:00
Ghost closed this pull request 2026-06-20 22:26:28 +03:00

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#8