Page tree browsing: content/<lang>/ tree UI with page selection and element list #4

Closed
opened 2026-07-11 12:59:07 +02:00 by dominik.polakovics · 1 comment

What to build

The TYPO3-style left pane, read-only (docs/DECISIONS.md D8, D17): read the content/<lang>/ directory (single language for now, but page identity is (path, lang) internally), render sections and leaf-bundle pages as a tree ordered by weight, and on selection show the page's element list (element type labels + icons from sidecars) in the center pane.

Acceptance criteria

  • Tree renders sections (_index.md) and leaf bundles from the sample site, ordered by weight then title
  • Disabled pages (draft: true) are visually distinct in the tree
  • Selecting a page lists its elements in order with schema-derived label/icon; selecting an element opens the form from #3
  • Tree state (expanded nodes, selection) survives a preview LiveReload
  • Internal page identity is (path, lang); no code path assumes a flat content/ root

Blocked by

## What to build The TYPO3-style left pane, read-only (docs/DECISIONS.md D8, D17): read the `content/<lang>/` directory (single language for now, but page identity is (path, lang) internally), render sections and leaf-bundle pages as a tree ordered by `weight`, and on selection show the page's element list (element type labels + icons from sidecars) in the center pane. ## Acceptance criteria - [ ] Tree renders sections (`_index.md`) and leaf bundles from the sample site, ordered by weight then title - [ ] Disabled pages (`draft: true`) are visually distinct in the tree - [ ] Selecting a page lists its elements in order with schema-derived label/icon; selecting an element opens the form from #3 - [ ] Tree state (expanded nodes, selection) survives a preview LiveReload - [ ] Internal page identity is (path, lang); no code path assumes a flat content/ root ## Blocked by - #1
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Replace the placeholder page-tree pane with a real, read-only tree of the site's content/<lang>/ directory — sections and leaf-bundle pages ordered by weight then title, drafts visually distinct — and on page selection show the page's element list (schema-derived labels + icons) in the center pane.

Dependency note: this issue is hard-blocked by #1 (walking skeleton), which is merged: it provides the harness binary, the embedded SolidJS shell with a placeholder tree pane, the supervised Hugo preview, and the /api/status polling pattern. It also consumes #2's shortcode round-trip engine (merged) for element lists. #3 (sidecar schemas + generated form) is not a hard block and may run concurrently — the scheduler handles ordering; see below for how to integrate with it either way. The issue body's docs/DECISIONS.md D8/D17 references point at a file that is not committed — this brief is self-contained.

Current behavior:
The tree pane in the SPA renders a hard-coded fake tree with a "Page tree coming soon" hint. No API exposes the content directory structure; the only content-related endpoint is a debug endpoint that returns one page's parsed element tree given a content-relative path. The content engine parses a file into a document whose front matter is kept as a raw, undecoded string — nothing currently extracts title, weight, or draft. The sample site is a flat posts/ section of plain .md files: no leaf bundles, no _index.md sections, no weights.

Desired behavior:

Tree data. The harness API gains an endpoint returning the page tree for a language. It walks the language's content root, building a nested structure of:

  • sections — directories containing _index.md
  • leaf-bundle pages — directories containing index.md
  • plain single-file .md pages, which existing sites contain (the sample site does today); the read-only view must not hide content, so these appear as leaf pages even though leaf bundles are the canonical form going forward (#9 only ever creates bundles)

Each node carries: page identity, kind (section vs page), title, weight, and draft, extracted by decoding the page's front matter. Siblings are ordered by ascending weight then title, with unweighted pages sorting after weighted ones (matching Hugo's ordering, so the tree mirrors site navigation). A page whose front matter fails to decode still appears in the tree (fallback title from its slug/filename) with the problem surfaced rather than breaking the whole tree.

Page identity is (path, lang). Internally and on the wire, a page is identified by the pair (content-relative path, language) — never by a bare path. The site is single-language for now and the language's content root is resolved in exactly one place, so a multilingual layout (content/en/, content/de/) later plugs in without reworking callers. No code path may assume a flat content/ root is the only shape. Paths crossing outside the content root are rejected.

Tree pane. The SPA tree pane renders this data with expand/collapse for sections and single selection. Disabled pages (draft: true) are visually distinct (e.g. dimmed and/or badged). This is read-only: no create/rename/delete affordances (that is #9).

Element list. Selecting a page fetches its parsed element tree and lists the page's elements in document order in the center pane. Shortcode elements display a label and icon derived from the element sidecar schemas (#3's schema API) when a matching schema exists; elements with no matching schema fall back to the shortcode name and a generic icon. Plain text/markdown runs between shortcodes may be shown as a generic body item or omitted — implementer's choice, but element ordering must be preserved.

#3 integration. Selecting an element in the list opens the schema-generated form from #3 if that feature is present in the codebase. If #3 has not landed yet, put the selection behind a minimal interface — a narrow "element selected" hook carrying the page identity plus the element's position within the page — with a placeholder implementation (e.g. a neutral detail pane), so #3's form can be wired in without reworking the tree or element list.

State survival. Expanded nodes and the current selection survive a preview LiveReload and any tree-data refresh: the preview iframe reloading must not disturb the SPA, and re-fetching the tree (poll or change-triggered refresh) must not collapse expansion or drop selection — follow the same "don't touch signals whose values haven't changed" discipline the status polling already uses. Nodes that disappeared on disk are pruned gracefully (a deleted selected page results in a cleared selection, not a broken UI).

Sample site. Extend the sample site so the feature is demonstrable and testable: at least one section with _index.md, leaf-bundle pages with weights that exercise the ordering, and a draft page (one already exists).

Key interfaces:

  • Tree endpoint: returns the nested section/page structure for a language; each node has identity (path, lang), kind, title, weight, draft flag, and children. Read-only, GET.
  • Page-elements endpoint: returns the element list for a page identified by (path, lang) — promoting or reusing the existing debug tree endpoint is acceptable; the response must let the UI map each element to a schema by shortcode name.
  • Front-matter metadata extraction (title/weight/draft) on top of the content engine's raw front matter — keep it reusable; #9 will need to write these same fields later.
  • Selection hook between element list and form pane: input is (page identity, element position); #3's form is the real consumer.

Acceptance criteria:

  • A harness API endpoint returns the page tree: sections (_index.md), leaf bundles, and existing plain .md pages, each with identity, kind, title, weight, and draft flag, ordered by weight then title (unweighted after weighted)
  • The SPA tree pane renders that data (placeholder removed) with expand/collapse and selection
  • Disabled pages (draft: true) are visually distinct in the tree
  • Selecting a page lists its elements in document order; elements show schema-derived label/icon when a sidecar schema matches, and fall back to the shortcode name otherwise
  • Selecting an element opens the form from #3 if present; otherwise the selection flows through a minimal hook with a placeholder pane, ready for #3 to consume
  • Expanded nodes and selection survive a preview LiveReload and a tree-data refresh
  • Page identity is (path, lang) in the API and the SPA; the language content root is resolved in one place; path traversal outside the content root is rejected
  • The sample site gains sections, weighted leaf bundles, and keeps a draft page; tree building and ordering are covered by tests
  • The feature is strictly read-only: no new write endpoint, nothing on disk is modified

Out of scope:

  • Any page mutation — create, rename, move, delete, reorder, draft toggle (#9); this tree is read-only
  • The sidecar schema system and the generated element form themselves (#3) — only consumed here if present
  • Element add/remove/reorder/nesting (#6)
  • Click-to-select from the preview iframe and viewport toggle (#16)
  • Rich-text body editing (#7, #8)
  • A language switcher or any multilingual UI — only the (path, lang) identity model is in scope
  • Media and image handling (#12)
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Replace the placeholder page-tree pane with a real, read-only tree of the site's `content/<lang>/` directory — sections and leaf-bundle pages ordered by weight then title, drafts visually distinct — and on page selection show the page's element list (schema-derived labels + icons) in the center pane. **Dependency note:** this issue is **hard-blocked by #1** (walking skeleton), which is merged: it provides the harness binary, the embedded SolidJS shell with a placeholder tree pane, the supervised Hugo preview, and the `/api/status` polling pattern. It also consumes #2's shortcode round-trip engine (merged) for element lists. #3 (sidecar schemas + generated form) is *not* a hard block and may run concurrently — the scheduler handles ordering; see below for how to integrate with it either way. The issue body's `docs/DECISIONS.md` D8/D17 references point at a file that is not committed — this brief is self-contained. **Current behavior:** The tree pane in the SPA renders a hard-coded fake tree with a "Page tree coming soon" hint. No API exposes the content directory structure; the only content-related endpoint is a debug endpoint that returns one page's parsed element tree given a content-relative path. The content engine parses a file into a document whose front matter is kept as a raw, undecoded string — nothing currently extracts `title`, `weight`, or `draft`. The sample site is a flat `posts/` section of plain `.md` files: no leaf bundles, no `_index.md` sections, no weights. **Desired behavior:** *Tree data.* The harness API gains an endpoint returning the page tree for a language. It walks the language's content root, building a nested structure of: - **sections** — directories containing `_index.md` - **leaf-bundle pages** — directories containing `index.md` - plain single-file `.md` pages, which existing sites contain (the sample site does today); the read-only view must not hide content, so these appear as leaf pages even though leaf bundles are the canonical form going forward (#9 only ever creates bundles) Each node carries: page identity, kind (section vs page), title, `weight`, and `draft`, extracted by decoding the page's front matter. Siblings are ordered by ascending weight then title, with unweighted pages sorting after weighted ones (matching Hugo's ordering, so the tree mirrors site navigation). A page whose front matter fails to decode still appears in the tree (fallback title from its slug/filename) with the problem surfaced rather than breaking the whole tree. *Page identity is (path, lang).* Internally and on the wire, a page is identified by the pair (content-relative path, language) — never by a bare path. The site is single-language for now and the language's content root is resolved in exactly one place, so a multilingual layout (`content/en/`, `content/de/`) later plugs in without reworking callers. No code path may assume a flat `content/` root is the only shape. Paths crossing outside the content root are rejected. *Tree pane.* The SPA tree pane renders this data with expand/collapse for sections and single selection. Disabled pages (`draft: true`) are visually distinct (e.g. dimmed and/or badged). This is read-only: no create/rename/delete affordances (that is #9). *Element list.* Selecting a page fetches its parsed element tree and lists the page's elements in document order in the center pane. Shortcode elements display a label and icon derived from the element sidecar schemas (#3's schema API) when a matching schema exists; elements with no matching schema fall back to the shortcode name and a generic icon. Plain text/markdown runs between shortcodes may be shown as a generic body item or omitted — implementer's choice, but element ordering must be preserved. *#3 integration.* Selecting an element in the list opens the schema-generated form from #3 **if that feature is present in the codebase**. If #3 has not landed yet, put the selection behind a minimal interface — a narrow "element selected" hook carrying the page identity plus the element's position within the page — with a placeholder implementation (e.g. a neutral detail pane), so #3's form can be wired in without reworking the tree or element list. *State survival.* Expanded nodes and the current selection survive a preview LiveReload and any tree-data refresh: the preview iframe reloading must not disturb the SPA, and re-fetching the tree (poll or change-triggered refresh) must not collapse expansion or drop selection — follow the same "don't touch signals whose values haven't changed" discipline the status polling already uses. Nodes that disappeared on disk are pruned gracefully (a deleted selected page results in a cleared selection, not a broken UI). *Sample site.* Extend the sample site so the feature is demonstrable and testable: at least one section with `_index.md`, leaf-bundle pages with `weight`s that exercise the ordering, and a draft page (one already exists). **Key interfaces:** - Tree endpoint: returns the nested section/page structure for a language; each node has identity (path, lang), kind, title, weight, draft flag, and children. Read-only, GET. - Page-elements endpoint: returns the element list for a page identified by (path, lang) — promoting or reusing the existing debug tree endpoint is acceptable; the response must let the UI map each element to a schema by shortcode name. - Front-matter metadata extraction (title/weight/draft) on top of the content engine's raw front matter — keep it reusable; #9 will need to *write* these same fields later. - Selection hook between element list and form pane: input is (page identity, element position); #3's form is the real consumer. **Acceptance criteria:** - [ ] A harness API endpoint returns the page tree: sections (`_index.md`), leaf bundles, and existing plain `.md` pages, each with identity, kind, title, weight, and draft flag, ordered by weight then title (unweighted after weighted) - [ ] The SPA tree pane renders that data (placeholder removed) with expand/collapse and selection - [ ] Disabled pages (`draft: true`) are visually distinct in the tree - [ ] Selecting a page lists its elements in document order; elements show schema-derived label/icon when a sidecar schema matches, and fall back to the shortcode name otherwise - [ ] Selecting an element opens the form from #3 if present; otherwise the selection flows through a minimal hook with a placeholder pane, ready for #3 to consume - [ ] Expanded nodes and selection survive a preview LiveReload and a tree-data refresh - [ ] Page identity is (path, lang) in the API and the SPA; the language content root is resolved in one place; path traversal outside the content root is rejected - [ ] The sample site gains sections, weighted leaf bundles, and keeps a draft page; tree building and ordering are covered by tests - [ ] The feature is strictly read-only: no new write endpoint, nothing on disk is modified **Out of scope:** - Any page mutation — create, rename, move, delete, reorder, draft toggle (#9); this tree is read-only - The sidecar schema system and the generated element form themselves (#3) — only consumed here if present - Element add/remove/reorder/nesting (#6) - Click-to-select from the preview iframe and viewport toggle (#16) - Rich-text body editing (#7, #8) - A language switcher or any multilingual UI — only the (path, lang) identity model is in scope - Media and image handling (#12)
Sign in to join this conversation.
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#4
No description provided.