Page management: create, rename, move, delete, reorder, enable/disable with page types #9

Closed
opened 2026-07-11 13:00:16 +02:00 by dominik.polakovics · 2 comments

What to build

Full tree editing with guardrails (docs/DECISIONS.md D8, D9, D10): create pages/sections constrained by developer-defined page types (sidecar YAML: allowed child types, allowed elements, front-matter fields); rename and move with automatic aliases: entries so published URLs never 404; delete with confirmation; drag & drop reorder writing weight; per-page enabled/disabled toggle mapping to draft: true. Every page is a leaf bundle. Slugs auto-generate from title, overridable.

Acceptance criteria

  • "New page" offers only page types allowed under the selected parent; creation writes a leaf bundle with defaults and draft: true
  • Rename/move rewrites the bundle path and appends the old URL to aliases; existing aliases are preserved
  • Drag & drop across and within sections updates weight (and moves bundles) consistently
  • Enable/disable toggles draft and updates the tree's visual state
  • Delete removes the bundle after a confirmation naming the page and its child count; sections with children cannot be deleted silently
  • Page-level front-matter fields declared by the page type (title, description/SEO) are editable in a generated form

Blocked by

## What to build Full tree editing with guardrails (docs/DECISIONS.md D8, D9, D10): create pages/sections constrained by developer-defined page types (sidecar YAML: allowed child types, allowed elements, front-matter fields); rename and move with automatic `aliases:` entries so published URLs never 404; delete with confirmation; drag & drop reorder writing `weight`; per-page enabled/disabled toggle mapping to `draft: true`. Every page is a leaf bundle. Slugs auto-generate from title, overridable. ## Acceptance criteria - [ ] "New page" offers only page types allowed under the selected parent; creation writes a leaf bundle with defaults and `draft: true` - [ ] Rename/move rewrites the bundle path and appends the old URL to `aliases`; existing aliases are preserved - [ ] Drag & drop across and within sections updates `weight` (and moves bundles) consistently - [ ] Enable/disable toggles `draft` and updates the tree's visual state - [ ] Delete removes the bundle after a confirmation naming the page and its child count; sections with children cannot be deleted silently - [ ] Page-level front-matter fields declared by the page type (title, description/SEO) are editable in a generated form ## Blocked by - #4
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Turn #4's read-only page tree into full page management: create pages/sections constrained by developer-defined page types (sidecar YAML), rename/move with automatic aliases: so published URLs never 404, drag & drop reordering that writes weight, a per-page enabled/disabled toggle mapping to draft, guarded delete, and a generated form for the page-level front-matter fields each page type declares.

Dependency note: this issue is hard-blocked by #4 (page tree browsing) — do not start it until #4 is merged. It builds directly on what #4 provides: the tree API and tree pane, the (path, lang) page identity used by every endpoint here, and front-matter metadata extraction (title/weight/draft). It also reuses machinery from #3 (sidecar-YAML loading/validation, the schema-driven form renderer, and the canonical save path through #2's round-trip engine) and from #2 itself; the scheduler handles ordering. The issue body's docs/DECISIONS.md D8/D9/D10 references point at a file that is not committed — this brief is self-contained.

Current behavior (once #4 lands):
The tree pane shows sections and pages read-only, ordered by weight then title, with drafts visually distinct; selecting a page lists its elements, and (per #3) element params are editable. But the tree itself cannot be changed from the tool: creating, renaming, moving, reordering, deleting, or toggling a page means hand-editing files and directories on disk. No page-type concept exists — nothing constrains what kinds of pages can be created where, and page-level front matter (title, description/SEO) has no form.

Desired behavior:

Page types. Developers define page types as sidecar YAML files under the site's editor config, one file per type (a sibling convention to #3's editor/elements/*.yaml — e.g. editor/pagetypes/*.yaml). Each page type declares: a human-readable label and icon; which page types are allowed as children beneath it; which element types its pages may contain; and its page-level front-matter fields, declared with the same field vocabulary as #3's element params (name, type, label, default, required, options) — title and description/SEO fields being the typical case. A suggested minimal shape (exact key naming is the implementer's choice; every concept must be representable):

label: Standard page
icon: page
children: [page, gallery]      # page types creatable beneath this one
elements: [hero, text, grid]   # parsed and served; enforced by element CRUD (#6)
fields:
  - name: title
    type: text
    label: Title
    required: true
  - name: description
    type: textarea
    label: SEO description

Loading follows #3's validation discipline: malformed files or unknown field types produce actionable errors surfaced via the API/UI while valid sidecars still load. There must be a way to express which types are allowed at the root of the content tree. A page records its type in front matter (Hugo's native type field is the natural fit), it round-trips through the canonical serializer, and the tree data exposes each node's type so the UI can offer only legal operations. The elements allow-list is parsed, validated against known element schemas, and served — enforcing it when adding elements is #6's job, not this issue's.

Create. "New page" (and "new section") under a selected parent offers only the page types the parent's type allows as children; at the tree root, only the root-allowed types. Creation asks for a title, auto-generates the slug from it (lowercased, hyphenated, collision-safe against existing siblings), lets the user override the slug, and writes a leaf bundle — a directory named by the slug containing the content file (index.md; _index.md for a section) — with the page type's front-matter defaults, its type recorded, and draft: true so new pages never publish accidentally. Every page this tool creates is a bundle; plain single-file pages are a legacy shape it reads (#4) but never writes.

Rename and move. Renaming (slug change) and moving to a different section both rewrite the bundle's path on disk — the whole bundle directory, including any page resources in it — and append the page's previous published URL to the aliases: list in its front matter so already-published URLs keep resolving. Existing aliases are preserved, duplicates are not added. Moving or renaming a section changes every descendant's URL too, so each descendant page gets its old URL appended to its own aliases — "published URLs never 404" is the invariant, not "one alias per operation".

Reorder. Drag & drop within a section reorders siblings by rewriting weight with a deterministic scheme (e.g. renumbering with gaps); drag & drop across sections is a move (path rewrite + alias) plus placement at the drop position. After any reorder, the tree order and Hugo's rendered order agree.

Enable/disable. A per-page toggle flips draft in front matter and immediately updates the node's visual state in the tree. Disabled pages remain visible in the preview (the supervised server builds drafts) — disabled means "not published", not "not previewable".

Delete. Deleting shows a confirmation that names the page and states its descendant count; a section with children can never be deleted without that count being explicit in the confirmation. Confirming removes the bundle directory (for a section, the subtree). There is no trash or undo — recoverability via git is #10.

Page settings form. Selecting a page (as opposed to one of its elements) offers a form for the front-matter fields its page type declares, generated by the same schema-driven renderer as #3's element forms, with the same validation behavior: server-side validation is authoritative, errors map inline to fields, and save writes canonically.

Write safety. Every front-matter mutation (aliases, weight, draft, type, declared fields) preserves front-matter keys the tool does not manage and leaves the page body untouched — all writes go through the canonical round-trip engine, never through string munging. Every operation validates that source and target stay inside the language's content root (reusing #4's identity/path discipline); a target that already exists is a clear error, never an overwrite. Failed operations leave no half-moved state behind where the filesystem allows it (rename over copy-delete). Successful operations reflect in the preview via LiveReload and in the tree without losing expansion state.

Key interfaces:

  • Page-type sidecar shape as above; its Go representation should live alongside #3's element-schema types and be loadable without the server.
  • Mutation API endpoints keyed by (path, lang): create, rename, move, delete, reorder, set-enabled, and save-page-front-matter. Validation errors come back in a shape the UI can map to a field or to the operation dialog.
  • Tree data (from #4) extended with each node's page type, so the UI can compute legal child types and drop targets; the server re-validates every operation regardless of what the UI offered.
  • Slug generation contract: title → filesystem-safe, URL-safe, sibling-unique slug; user-overridable before creation.
  • Alias computation: the page's previous published URL(s), derived from its content path; appended to the front-matter aliases list.

Acceptance criteria:

  • Page-type sidecar YAML files are discovered and validated with actionable errors (invalid files don't block valid ones) and served via the API; pages carry their type in front matter and it round-trips
  • "New page" offers only page types allowed under the selected parent (root-level allowance included); creation writes a leaf bundle with the type's defaults, the recorded type, and draft: true; slugs auto-generate from title, are collision-safe, and can be overridden
  • Rename/move rewrites the bundle path (including bundle resources) and appends the old URL to aliases; existing aliases are preserved; renaming/moving a section aliases every descendant's old URL
  • Drag & drop within a section rewrites weight; across sections it moves the bundle and places it at the drop position; resulting tree order matches Hugo's render order
  • The enable/disable toggle flips draft and the tree's visual state updates immediately
  • Delete requires a confirmation naming the page and its descendant count; sections with children cannot be deleted without that count shown; confirming removes the bundle/subtree
  • Page-level front-matter fields declared by the page type are editable in a schema-generated form with inline validation; saves are canonical
  • Every write preserves unmanaged front-matter keys and the page body; operations are confined to the content root; existing targets are never overwritten
  • The preview reflects each operation via LiveReload, and tree expansion/selection state survives the resulting refresh
  • The sample site defines at least two page types exercising the child-type constraints; create/rename/move/reorder/toggle/delete are covered by tests (against temp copies of the site, not the checked-in one)

Out of scope:

  • Adding, removing, reordering, or nesting elements within a page (#6) — the page type's elements allow-list is parsed and served here, enforced there
  • Git safety commits, per-page publish, and discard (#10) and publish feedback (#11) — writes here go straight to disk
  • Locks, identity, CSRF (#13) — no concurrency guarding in this slice
  • Media uploads and bundle resource management (#12) — bundles are moved wholesale, nothing more
  • Multi-language operations — identity carries lang (from #4), but the UI manages a single language
  • Undo, trash, or version history for deletes
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Turn #4's read-only page tree into full page management: create pages/sections constrained by developer-defined page types (sidecar YAML), rename/move with automatic `aliases:` so published URLs never 404, drag & drop reordering that writes `weight`, a per-page enabled/disabled toggle mapping to `draft`, guarded delete, and a generated form for the page-level front-matter fields each page type declares. **Dependency note:** this issue is **hard-blocked by #4** (page tree browsing) — do not start it until #4 is merged. It builds directly on what #4 provides: the tree API and tree pane, the (path, lang) page identity used by every endpoint here, and front-matter metadata extraction (title/weight/draft). It also reuses machinery from #3 (sidecar-YAML loading/validation, the schema-driven form renderer, and the canonical save path through #2's round-trip engine) and from #2 itself; the scheduler handles ordering. The issue body's `docs/DECISIONS.md` D8/D9/D10 references point at a file that is not committed — this brief is self-contained. **Current behavior (once #4 lands):** The tree pane shows sections and pages read-only, ordered by weight then title, with drafts visually distinct; selecting a page lists its elements, and (per #3) element params are editable. But the tree itself cannot be changed from the tool: creating, renaming, moving, reordering, deleting, or toggling a page means hand-editing files and directories on disk. No page-type concept exists — nothing constrains what kinds of pages can be created where, and page-level front matter (title, description/SEO) has no form. **Desired behavior:** *Page types.* Developers define page types as sidecar YAML files under the site's editor config, one file per type (a sibling convention to #3's `editor/elements/*.yaml` — e.g. `editor/pagetypes/*.yaml`). Each page type declares: a human-readable label and icon; which page types are allowed as children beneath it; which element types its pages may contain; and its page-level front-matter fields, declared with the same field vocabulary as #3's element params (name, type, label, default, required, options) — title and description/SEO fields being the typical case. A suggested minimal shape (exact key naming is the implementer's choice; every concept must be representable): ```yaml label: Standard page icon: page children: [page, gallery] # page types creatable beneath this one elements: [hero, text, grid] # parsed and served; enforced by element CRUD (#6) fields: - name: title type: text label: Title required: true - name: description type: textarea label: SEO description ``` Loading follows #3's validation discipline: malformed files or unknown field types produce actionable errors surfaced via the API/UI while valid sidecars still load. There must be a way to express which types are allowed at the root of the content tree. A page records its type in front matter (Hugo's native `type` field is the natural fit), it round-trips through the canonical serializer, and the tree data exposes each node's type so the UI can offer only legal operations. The `elements` allow-list is parsed, validated against known element schemas, and served — enforcing it when adding elements is #6's job, not this issue's. *Create.* "New page" (and "new section") under a selected parent offers only the page types the parent's type allows as children; at the tree root, only the root-allowed types. Creation asks for a title, auto-generates the slug from it (lowercased, hyphenated, collision-safe against existing siblings), lets the user override the slug, and writes a **leaf bundle** — a directory named by the slug containing the content file (`index.md`; `_index.md` for a section) — with the page type's front-matter defaults, its type recorded, and `draft: true` so new pages never publish accidentally. Every page this tool creates is a bundle; plain single-file pages are a legacy shape it reads (#4) but never writes. *Rename and move.* Renaming (slug change) and moving to a different section both rewrite the bundle's path on disk — the whole bundle directory, including any page resources in it — and append the page's previous published URL to the `aliases:` list in its front matter so already-published URLs keep resolving. Existing aliases are preserved, duplicates are not added. Moving or renaming a **section** changes every descendant's URL too, so each descendant page gets its old URL appended to its own aliases — "published URLs never 404" is the invariant, not "one alias per operation". *Reorder.* Drag & drop within a section reorders siblings by rewriting `weight` with a deterministic scheme (e.g. renumbering with gaps); drag & drop across sections is a move (path rewrite + alias) plus placement at the drop position. After any reorder, the tree order and Hugo's rendered order agree. *Enable/disable.* A per-page toggle flips `draft` in front matter and immediately updates the node's visual state in the tree. Disabled pages remain visible in the preview (the supervised server builds drafts) — disabled means "not published", not "not previewable". *Delete.* Deleting shows a confirmation that names the page and states its descendant count; a section with children can never be deleted without that count being explicit in the confirmation. Confirming removes the bundle directory (for a section, the subtree). There is no trash or undo — recoverability via git is #10. *Page settings form.* Selecting a page (as opposed to one of its elements) offers a form for the front-matter fields its page type declares, generated by the same schema-driven renderer as #3's element forms, with the same validation behavior: server-side validation is authoritative, errors map inline to fields, and save writes canonically. *Write safety.* Every front-matter mutation (aliases, weight, draft, type, declared fields) preserves front-matter keys the tool does not manage and leaves the page body untouched — all writes go through the canonical round-trip engine, never through string munging. Every operation validates that source and target stay inside the language's content root (reusing #4's identity/path discipline); a target that already exists is a clear error, never an overwrite. Failed operations leave no half-moved state behind where the filesystem allows it (rename over copy-delete). Successful operations reflect in the preview via LiveReload and in the tree without losing expansion state. **Key interfaces:** - Page-type sidecar shape as above; its Go representation should live alongside #3's element-schema types and be loadable without the server. - Mutation API endpoints keyed by (path, lang): create, rename, move, delete, reorder, set-enabled, and save-page-front-matter. Validation errors come back in a shape the UI can map to a field or to the operation dialog. - Tree data (from #4) extended with each node's page type, so the UI can compute legal child types and drop targets; the server re-validates every operation regardless of what the UI offered. - Slug generation contract: title → filesystem-safe, URL-safe, sibling-unique slug; user-overridable before creation. - Alias computation: the page's previous published URL(s), derived from its content path; appended to the front-matter `aliases` list. **Acceptance criteria:** - [ ] Page-type sidecar YAML files are discovered and validated with actionable errors (invalid files don't block valid ones) and served via the API; pages carry their type in front matter and it round-trips - [ ] "New page" offers only page types allowed under the selected parent (root-level allowance included); creation writes a leaf bundle with the type's defaults, the recorded type, and `draft: true`; slugs auto-generate from title, are collision-safe, and can be overridden - [ ] Rename/move rewrites the bundle path (including bundle resources) and appends the old URL to `aliases`; existing aliases are preserved; renaming/moving a section aliases every descendant's old URL - [ ] Drag & drop within a section rewrites `weight`; across sections it moves the bundle and places it at the drop position; resulting tree order matches Hugo's render order - [ ] The enable/disable toggle flips `draft` and the tree's visual state updates immediately - [ ] Delete requires a confirmation naming the page and its descendant count; sections with children cannot be deleted without that count shown; confirming removes the bundle/subtree - [ ] Page-level front-matter fields declared by the page type are editable in a schema-generated form with inline validation; saves are canonical - [ ] Every write preserves unmanaged front-matter keys and the page body; operations are confined to the content root; existing targets are never overwritten - [ ] The preview reflects each operation via LiveReload, and tree expansion/selection state survives the resulting refresh - [ ] The sample site defines at least two page types exercising the child-type constraints; create/rename/move/reorder/toggle/delete are covered by tests (against temp copies of the site, not the checked-in one) **Out of scope:** - Adding, removing, reordering, or nesting **elements** within a page (#6) — the page type's `elements` allow-list is parsed and served here, enforced there - Git safety commits, per-page publish, and discard (#10) and publish feedback (#11) — writes here go straight to disk - Locks, identity, CSRF (#13) — no concurrency guarding in this slice - Media uploads and bundle resource management (#12) — bundles are moved wholesale, nothing more - Multi-language operations — identity carries lang (from #4), but the UI manages a single language - Undo, trash, or version history for deletes
Author
Owner

Closing as superseded: the maintainer has decided on a clean-slate reset of the project (issue #29, PR #30). The v1 implementation this issue builds on is being removed and the plan will be re-drawn from scratch; this spec is retired rather than actioned.

Closing as superseded: the maintainer has decided on a clean-slate reset of the project (issue #29, PR #30). The v1 implementation this issue builds on is being removed and the plan will be re-drawn from scratch; this spec is retired rather than actioned.
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#9
No description provided.