Edit one element end-to-end: sidecar schema → generated form → canonical save → live preview #3

Closed
opened 2026-07-11 12:58:53 +02:00 by dominik.polakovics · 2 comments

What to build

The first true tracer bullet (docs/DECISIONS.md D6): load element sidecar schemas from editor/elements/*.yaml (label, icon, group, version, params with types, body policy, placement), expose them via API, render a schema-generated param form in the SPA for one element on one page, and on Save write the canonically serialized file so the preview updates via LiveReload. v1 field types: text, textarea, link, image (path string for now), select, checkbox, number, color, date.

Acceptance criteria

  • Sidecar YAML files are discovered, validated (unknown types/malformed files produce actionable errors listed in the UI), and served via API
  • Selecting an element on a page renders a form generated purely from its schema (labels, defaults, required, select options)
  • Save round-trips through the engine from #2 and only rewrites that page's file, canonically formatted
  • Preview iframe reflects the change without manual reload
  • Validation errors (required missing, wrong type) block save and are shown inline on the field
  • Works against the sample site with at least two element types defined

Blocked by

## What to build The first true tracer bullet (docs/DECISIONS.md D6): load element sidecar schemas from `editor/elements/*.yaml` (label, icon, group, version, params with types, body policy, placement), expose them via API, render a schema-generated param form in the SPA for one element on one page, and on Save write the canonically serialized file so the preview updates via LiveReload. v1 field types: text, textarea, link, image (path string for now), select, checkbox, number, color, date. ## Acceptance criteria - [ ] Sidecar YAML files are discovered, validated (unknown types/malformed files produce actionable errors listed in the UI), and served via API - [ ] Selecting an element on a page renders a form generated purely from its schema (labels, defaults, required, select options) - [ ] Save round-trips through the engine from #2 and only rewrites that page's file, canonically formatted - [ ] Preview iframe reflects the change without manual reload - [ ] Validation errors (required missing, wrong type) block save and are shown inline on the field - [ ] Works against the sample site with at least two element types defined ## Blocked by - #1 - #2
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: First editing tracer bullet: load element sidecar schemas from editor/elements/*.yaml, serve them via the harness API, render a schema-generated param form in the SPA for one element on one page, and on Save write the canonically serialized file (via the engine from #2) so the preview updates through LiveReload.

Dependency note: this issue is hard-blocked by #2 (shortcode round-trip engine). Do not start it until #2 is merged; the save path must go through that engine, not a parallel serializer. The issue body's docs/DECISIONS.md D6 reference points at a file that is not committed — this brief is self-contained.

Current behavior:
The harness (from #1) serves the SolidJS shell with a placeholder form pane and a live preview iframe, and (once #2 lands) can parse a content file into an element tree and serialize it canonically, with a JSON debug endpoint. There is no schema system, no form generation, and no write path: nothing in the tool can yet modify a content file. The sample site has no sidecar schemas and (before #2's corpus) no shortcode-based content.

Desired behavior:

Sidecar schemas. The tool discovers element schemas from editor/elements/*.yaml under the site directory (one file per element type, filename matching the shortcode name). Each sidecar describes: a human-readable label, an icon identifier, a picker group, a schema version, a body policy (whether the element has a body), a placement declaration, and a list of params. Each param has a name, a field type, a label, and optionally a default, required flag, and (for selects) options. The nine v1 field types: text, textarea, link, image (a plain path string in this slice), select, checkbox, number, color, date. A suggested minimal shape (exact key naming is the implementer's choice, but every concept below must be representable):

label: Hero
icon: hero
group: content
version: 1
body: markdown        # or: none
placement: {}         # parsed and served; semantics enforced later (#6)
params:
  - name: title
    type: text
    label: Title
    required: true
  - name: align
    type: select
    label: Alignment
    options: [left, center, right]
    default: left

Schema loading is validated: a malformed YAML file, an unknown field type, or a structurally invalid sidecar must not crash the tool — it produces an actionable error (naming the file and the problem) that is exposed via the API and listed in the UI. Valid sidecars still load when invalid ones are present.

API. The harness API exposes: the loaded element schemas (with any load errors), a page's element tree (from #2's parser), and a save operation for a page that accepts the edited element data, validates it against the schema, and writes the canonically serialized file. Save rewrites only that one page's file and nothing else on disk.

Form generation. Selecting an element on a page renders a form generated purely from its schema — labels, defaults, required markers, select options all come from the sidecar. There is no per-element hand-written form code; a new sidecar dropped into editor/elements/ gets a working form with zero SPA changes. Each of the nine field types renders as an appropriate input. How the user reaches the element is minimal in this slice: a crude element list for one page (e.g. derived from the parsed tree of the currently previewed sample page) is sufficient — the real tree/element-list navigation is #4.

Validation. A required param that is missing/empty, or a value that doesn't fit the field type (e.g. non-numeric in number), blocks the save and is shown inline on the offending field. Server-side validation is authoritative; inline display in the form is required either way.

Preview. After a successful save, the preview iframe reflects the change via Hugo LiveReload without a manual reload and without the tool restarting Hugo.

Sample site. The sample site gains at least two element types — each with a Hugo shortcode template that renders visibly and a matching sidecar — plus at least one content page using them, so the end-to-end flow is demonstrable and testable. Minimal markup is fine; the full element library is #14.

Key interfaces:

  • Sidecar schema shape as above; the Go type that represents it will be reused by later issues (picker in #6, full library in #14) — keep it in a package importable without the server.
  • Schema-driven form renderer in the SPA: input = schema + current param values, output = edited values + client-side validity. No element-specific branches.
  • Save endpoint contract: page identifier + edited element data in, canonical file written via #2's serializer, validation errors returned in a shape the form can map to individual fields.
  • placement and version are parsed, validated for basic well-formedness, and served — their behavior (nesting rules, migrations) belongs to later issues.

Acceptance criteria:

  • Sidecar YAML files in editor/elements/ are discovered and validated; unknown field types or malformed files produce actionable errors listed in the UI while valid sidecars still load
  • Element schemas are served via the harness API
  • Selecting an element on a page renders a form generated purely from its schema (labels, defaults, required, select options); all nine v1 field types render
  • Save round-trips through the engine from #2 and rewrites only that page's file, canonically formatted
  • The preview iframe reflects a saved change without manual reload
  • Validation errors (required missing, wrong type) block save and appear inline on the field
  • The sample site defines at least two element types (template + sidecar) and a page using them; the end-to-end flow is covered by tests

Out of scope:

  • Page tree browsing and the real element-list navigation (#4) — a minimal selection mechanism suffices here
  • Adding, removing, reordering, or nesting elements (#6) — editing params of existing elements only
  • Rich-text editing of bodies (#7) and inline shortcode chips (#8) — a body, if edited at all in this slice, may be a plain textarea
  • The full v1 element library (#14) and its styling (#15)
  • Media library and image picker UI (#12) — image fields are plain path strings
  • Git commits on save (#10) — writing the file to disk is enough here
  • Locks, identity, CSRF (#13); click-to-select from the preview (#16)
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** First editing tracer bullet: load element sidecar schemas from `editor/elements/*.yaml`, serve them via the harness API, render a schema-generated param form in the SPA for one element on one page, and on Save write the canonically serialized file (via the engine from #2) so the preview updates through LiveReload. **Dependency note:** this issue is **hard-blocked by #2** (shortcode round-trip engine). Do not start it until #2 is merged; the save path must go through that engine, not a parallel serializer. The issue body's `docs/DECISIONS.md D6` reference points at a file that is not committed — this brief is self-contained. **Current behavior:** The harness (from #1) serves the SolidJS shell with a placeholder form pane and a live preview iframe, and (once #2 lands) can parse a content file into an element tree and serialize it canonically, with a JSON debug endpoint. There is no schema system, no form generation, and no write path: nothing in the tool can yet modify a content file. The sample site has no sidecar schemas and (before #2's corpus) no shortcode-based content. **Desired behavior:** *Sidecar schemas.* The tool discovers element schemas from `editor/elements/*.yaml` under the site directory (one file per element type, filename matching the shortcode name). Each sidecar describes: a human-readable label, an icon identifier, a picker group, a schema version, a body policy (whether the element has a body), a placement declaration, and a list of params. Each param has a name, a field type, a label, and optionally a default, required flag, and (for selects) options. The nine v1 field types: `text`, `textarea`, `link`, `image` (a plain path string in this slice), `select`, `checkbox`, `number`, `color`, `date`. A suggested minimal shape (exact key naming is the implementer's choice, but every concept below must be representable): ```yaml label: Hero icon: hero group: content version: 1 body: markdown # or: none placement: {} # parsed and served; semantics enforced later (#6) params: - name: title type: text label: Title required: true - name: align type: select label: Alignment options: [left, center, right] default: left ``` Schema loading is validated: a malformed YAML file, an unknown field type, or a structurally invalid sidecar must not crash the tool — it produces an actionable error (naming the file and the problem) that is exposed via the API and listed in the UI. Valid sidecars still load when invalid ones are present. *API.* The harness API exposes: the loaded element schemas (with any load errors), a page's element tree (from #2's parser), and a save operation for a page that accepts the edited element data, validates it against the schema, and writes the canonically serialized file. Save rewrites only that one page's file and nothing else on disk. *Form generation.* Selecting an element on a page renders a form generated purely from its schema — labels, defaults, required markers, select options all come from the sidecar. There is no per-element hand-written form code; a new sidecar dropped into `editor/elements/` gets a working form with zero SPA changes. Each of the nine field types renders as an appropriate input. How the user reaches the element is minimal in this slice: a crude element list for one page (e.g. derived from the parsed tree of the currently previewed sample page) is sufficient — the real tree/element-list navigation is #4. *Validation.* A required param that is missing/empty, or a value that doesn't fit the field type (e.g. non-numeric in `number`), blocks the save and is shown inline on the offending field. Server-side validation is authoritative; inline display in the form is required either way. *Preview.* After a successful save, the preview iframe reflects the change via Hugo LiveReload without a manual reload and without the tool restarting Hugo. *Sample site.* The sample site gains at least two element types — each with a Hugo shortcode template that renders visibly and a matching sidecar — plus at least one content page using them, so the end-to-end flow is demonstrable and testable. Minimal markup is fine; the full element library is #14. **Key interfaces:** - Sidecar schema shape as above; the Go type that represents it will be reused by later issues (picker in #6, full library in #14) — keep it in a package importable without the server. - Schema-driven form renderer in the SPA: input = schema + current param values, output = edited values + client-side validity. No element-specific branches. - Save endpoint contract: page identifier + edited element data in, canonical file written via #2's serializer, validation errors returned in a shape the form can map to individual fields. - `placement` and `version` are parsed, validated for basic well-formedness, and served — their behavior (nesting rules, migrations) belongs to later issues. **Acceptance criteria:** - [ ] Sidecar YAML files in `editor/elements/` are discovered and validated; unknown field types or malformed files produce actionable errors listed in the UI while valid sidecars still load - [ ] Element schemas are served via the harness API - [ ] Selecting an element on a page renders a form generated purely from its schema (labels, defaults, required, select options); all nine v1 field types render - [ ] Save round-trips through the engine from #2 and rewrites only that page's file, canonically formatted - [ ] The preview iframe reflects a saved change without manual reload - [ ] Validation errors (required missing, wrong type) block save and appear inline on the field - [ ] The sample site defines at least two element types (template + sidecar) and a page using them; the end-to-end flow is covered by tests **Out of scope:** - Page tree browsing and the real element-list navigation (#4) — a minimal selection mechanism suffices here - Adding, removing, reordering, or nesting elements (#6) — editing params of existing elements only - Rich-text editing of bodies (#7) and inline shortcode chips (#8) — a body, if edited at all in this slice, may be a plain textarea - The full v1 element library (#14) and its styling (#15) - Media library and image picker UI (#12) — image fields are plain path strings - Git commits on save (#10) — writing the file to disk is enough here - Locks, identity, CSRF (#13); click-to-select from the preview (#16)
Author
Owner

This was generated by AI while landing a PR.

Landing audit — PR #20 (afk/3)

Verdict: PASS.

Verification signal relied on: Forgejo Actions CI build-and-test (pull_request)green (ci / build-and-test, Successful in 1m4s). Not re-run. Its run covers go vet ./..., go test ./..., npx tsc --noEmit, npm test, npm run build.

Checks performed:

  • Convention lint — title feat: edit one element end-to-end … is valid Conventional Commits. ✓
  • AFK contract — head branch afk/3; body carries a working Closes #3. ✓
  • Mergeabilitymain HEAD == merge-base; merge is clean, no conflict. ✓
  • Diff review (correctness):
    • internal/element — strict sidecar decode (unknown keys rejected), actionable per-file load errors, valid sidecars survive alongside invalid ones; nine field types; ValidateValues/CoerceValues cover required/type/select/date and int-vs-float coercion. ✓
    • internal/server save path — content-relative path with filepath.IsLocal traversal guard, resolveShortcode returns 409 on drift, server-authoritative 422 field errors, mergeParams preserves non-schema params, atomicWriteFile (same-dir temp + chmod + rename) writes only the target page. ✓
  • Acceptance criteria — all six mapped to evidence in the PR body and code/tests.

Minor note (non-blocking): string params — including textarea — reject line breaks. Defensible, since shortcode params serialize on a single line; rich-body editing is explicitly deferred to #7.

Awaiting free-text merge confirmation from the human.

> *This was generated by AI while landing a PR.* ## Landing audit — PR #20 (`afk/3`) **Verdict: PASS.** **Verification signal relied on:** Forgejo Actions CI `build-and-test (pull_request)` — **green** (`ci / build-and-test`, Successful in 1m4s). Not re-run. Its run covers `go vet ./...`, `go test ./...`, `npx tsc --noEmit`, `npm test`, `npm run build`. **Checks performed:** - **Convention lint** — title `feat: edit one element end-to-end …` is valid Conventional Commits. ✓ - **AFK contract** — head branch `afk/3`; body carries a working `Closes #3`. ✓ - **Mergeability** — `main` HEAD == merge-base; merge is clean, no conflict. ✓ - **Diff review (correctness):** - `internal/element` — strict sidecar decode (unknown keys rejected), actionable per-file load errors, valid sidecars survive alongside invalid ones; nine field types; `ValidateValues`/`CoerceValues` cover required/type/select/date and int-vs-float coercion. ✓ - `internal/server` save path — content-relative path with `filepath.IsLocal` traversal guard, `resolveShortcode` returns 409 on drift, server-authoritative 422 field errors, `mergeParams` preserves non-schema params, `atomicWriteFile` (same-dir temp + chmod + rename) writes only the target page. ✓ - **Acceptance criteria** — all six mapped to evidence in the PR body and code/tests. **Minor note (non-blocking):** string params — including `textarea` — reject line breaks. Defensible, since shortcode params serialize on a single line; rich-body editing is explicitly deferred to #7. Awaiting free-text merge confirmation from the human.
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#3
No description provided.