feat(web): media folder tree joins the shell's tree column #110

Merged
dominik.polakovics merged 3 commits from lab/20260718-0231 into main 2026-07-18 13:36:50 +02:00

The file manager's folder tree was not in the same container as the page tree — it could not share the column's width and could not collapse. This unifies them, per the design agreed in the issue.

What changed

  • TreeColumn is now the shell's generic tree column: it keeps width (one shared deckle:tree-width key, 180–560px), the resizer and its a11y surface; the pane content comes from the active module. deckle:media-tree-width and the copy-pasted resize logic are gone.
  • Registry API: ModuleDef.tree: booleannav?: { label, pane }. page/view/list/edit share one PAGE_TREE_NAV entry (a PageTreeNav rendering TreePane from ctx — the tree data stays shell-owned); media declares MediaFolderNav. No nav = no column, rail toggle hidden.
  • Collapse: the existing rail toggle + deckle:tree-collapsed flag now govern whichever tree is active — the media tree collapses for the first time.
  • provider registry entry: the shell wraps it around the tree column and the module body. MediaProvider carries the state the two sibling subtrees share (folders/listing resources, menus, dialogs, drag-in-flight) — created per module activation, so transient state still resets on a switch.
  • Folder expand/collapse (page-tree parity): chevrons per folder row, persisted expanded-set under deckle:media-tree-expanded, root open on first load, and the URL folder's ancestors auto-expand so a deep link is never hidden.

Verification

  • tsc --noEmit, full vitest suite (871 passing, incl. 5 new media-tree tests), go test ./... all green.
  • Real-Chromium pass against harness serve (playwright-core): shared width across module switch and reload, rail collapse/restore on the media tree, chevron expand/collapse, deep-link ancestor expansion, tree-row context menu opening a body dialog, and a grid-tile drag onto a tree folder row — now crossing sibling render subtrees — moving the file on disk.

Not included (deliberately): drag-hover auto-expand of collapsed folders during a file drag (the page tree has this; can follow up if wanted).

Closes #109

🤖 Generated with Claude Code

The file manager's folder tree was not in the same container as the page tree — it could not share the column's width and could not collapse. This unifies them, per the design agreed in the issue. ## What changed - **`TreeColumn` is now the shell's generic tree column**: it keeps width (one shared `deckle:tree-width` key, 180–560px), the resizer and its a11y surface; the pane content comes from the active module. `deckle:media-tree-width` and the copy-pasted resize logic are gone. - **Registry API**: `ModuleDef.tree: boolean` → `nav?: { label, pane }`. page/view/list/edit share one `PAGE_TREE_NAV` entry (a `PageTreeNav` rendering `TreePane` from ctx — the tree data stays shell-owned); media declares `MediaFolderNav`. No `nav` = no column, rail toggle hidden. - **Collapse**: the existing rail toggle + `deckle:tree-collapsed` flag now govern whichever tree is active — the media tree collapses for the first time. - **`provider` registry entry**: the shell wraps it around the tree column and the module body. MediaProvider carries the state the two sibling subtrees share (folders/listing resources, menus, dialogs, drag-in-flight) — created per module activation, so transient state still resets on a switch. - **Folder expand/collapse** (page-tree parity): chevrons per folder row, persisted expanded-set under `deckle:media-tree-expanded`, root open on first load, and the URL folder's ancestors auto-expand so a deep link is never hidden. ## Verification - `tsc --noEmit`, full vitest suite (871 passing, incl. 5 new media-tree tests), `go test ./...` all green. - Real-Chromium pass against `harness serve` (playwright-core): shared width across module switch and reload, rail collapse/restore on the media tree, chevron expand/collapse, deep-link ancestor expansion, tree-row context menu opening a body dialog, and a grid-tile drag onto a tree folder row — now crossing sibling render subtrees — moving the file on disk. Not included (deliberately): drag-hover auto-expand of collapsed folders during a file drag (the page tree has this; can follow up if wanted). Closes #109 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(web): media folder tree joins the shell's tree column (#109)
All checks were successful
ci / build-and-test (pull_request) Successful in 5m49s
0d0c042ec2
The media module's folder tree was a copy-paste mirror of the page tree
column: own width key, duplicated resize logic, mirrored CSS, no
collapse. The registry's `tree: boolean` becomes `nav` (an accessible
label plus a pane component), the shell's TreeColumn turns generic
(children + one shared width key), and collapse from the rail now
governs whichever tree is active. A module whose nav pane and body share
transient state (media's drags, menus, dialogs) declares a `provider`
the shell wraps around both siblings.

Folder rows gain page-tree-parity expand/collapse: chevrons, a
persisted expanded-set (deckle:media-tree-expanded), root open on first
load, and auto-expansion of the URL folder's ancestors.

Verified in a real Chromium against `harness serve`: shared width across
module switches, rail collapse on the media tree, chevron behavior,
deep-link ancestor expansion, and a grid-tile drag onto a tree folder
row (now crossing sibling subtrees) moving the file on disk.

Closes #109

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

This was generated by AI while landing a PR.

Landed. Verdict: PASS → merged.

Verification signal relied on: the PR's Forgejo Actions run ci / build-and-test (success, 5m49s). Not re-run locally — nothing in the branch changed after it.

What was checked

  • Mergeable against main with no conflicts (no resolution commit, so the green CI covers exactly the merged tree).
  • Title is Conventional Commits; body carries a working Closes #109.
  • Full diff read across all 7 files against the design agreed in #109.
  • Dead-code sweep: deckle:media-tree-width, .media-tree-column* and the duplicated resize logic are gone from the branch.
  • a11y: the resizer keeps role=separator + aria-valuenow/min/max; its label is now parameterised per module (Resize ${resizeLabel}).
  • Lifecycle: createMediaState runs once per MediaProvider mount, and the provider swaps via Dynamic on module change — transient state does reset on a switch, as claimed. The nav <Show keyed> is keyed on the registry entry, so page-tree modules keep one column mounted across a switch.
  • Expansion state: keyed by folder path (survives a tree rebuild); the ancestor-expand effect tracks only displayFolder() and writes through a functional setter, so it cannot loop.
  • Tests: 5 new cases, one per user-visible claim.

Noted, not blocking: issue #109 item 6 specified moving the grid↔tree drag state to file-level signals; the PR uses a context provider instead. That is the better call — file-level signals would outlive a module switch, which the provider's mount/unmount avoids — and the PR body states the substitution openly.

Caveat, for the record: a second independent diff-review pass was still running when the merge was authorised, so it did not contribute to this verdict.

> *This was generated by AI while landing a PR.* **Landed.** Verdict: PASS → merged. **Verification signal relied on:** the PR's Forgejo Actions run `ci / build-and-test` (success, 5m49s). Not re-run locally — nothing in the branch changed after it. **What was checked** - Mergeable against `main` with no conflicts (no resolution commit, so the green CI covers exactly the merged tree). - Title is Conventional Commits; body carries a working `Closes #109`. - Full diff read across all 7 files against the design agreed in #109. - Dead-code sweep: `deckle:media-tree-width`, `.media-tree-column*` and the duplicated resize logic are gone from the branch. - a11y: the resizer keeps `role=separator` + `aria-valuenow/min/max`; its label is now parameterised per module (`Resize ${resizeLabel}`). - Lifecycle: `createMediaState` runs once per `MediaProvider` mount, and the provider swaps via `Dynamic` on module change — transient state does reset on a switch, as claimed. The nav `<Show keyed>` is keyed on the registry entry, so page-tree modules keep one column mounted across a switch. - Expansion state: keyed by folder path (survives a tree rebuild); the ancestor-expand effect tracks only `displayFolder()` and writes through a functional setter, so it cannot loop. - Tests: 5 new cases, one per user-visible claim. **Noted, not blocking:** issue #109 item 6 specified moving the grid↔tree drag state to file-level signals; the PR uses a context provider instead. That is the better call — file-level signals would outlive a module switch, which the provider's mount/unmount avoids — and the PR body states the substitution openly. **Caveat, for the record:** a second independent diff-review pass was still running when the merge was authorised, so it did not contribute to this verdict.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!110
No description provided.