feat(forms): forms bundle — headless form pages, field elements, a Forms module, and a Form element #323
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!323
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/322"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #322
What ships
Engine: the
headlesspage-type keyeditor/pagetypes/<name>.yamlacceptsheadless: true. A value that is not a bool is a load error.harness sidecarandharness migratekeep the key canonical. It sits aftersingletonin the key order.build: {list: never, render: never}after the type'sdefaults. This applies in the tree, in a module and for the home page.internal/pagetype/headless.go(HeadlessBuild,CreationChanges,BuildChange,LacksBuild).internal/contentcan now write a flatmap[string]string: a YAML block mapping, a TOML inline table, or a JSON object.permalink, which is whatpageparams and link pickers store, so it stays pickable. It getsaddress: ""andheadless: true, so View and the preview see no address.harness check: warns, naming the file, on a page or translation of a headless type whose front matter lacksbuild.buildon a headless type is a load error..Pages, andsite.GetPageplus.Contentrenders it.Editor
ViewButtonand reads the server'sheadlessverdict.Bundle
scaffold/features/forms(40 files)formsandformpage types, both headless.formsis a singleton, hidden, withchildren: [form].formallows only the eight field elements.form-field-*orform-text, placeable only on a form page.formelement, withform(a page param,pageType: form) andaction.partials/forms/spec.htmlreturns the spec slice in document order, with no duplicates. A store guard on the input name keeps it duplicate-free.partials/forms/render.htmldraws the form:f-<uid>;websitehoneypot, then the submit button;<key>-sentand<key>-errorwith the:targetreveal;maxlengthcaps of 200, 254, 50 and 4000, andautocompleteon email and phone.ce/uid.html,ce/bool.html,backendlayout/body.htmlandbackendlayout/links.html.ce/attrs.html,ce/disabled.htmlandbackendlayout/render.htmlcall them.forms-form__*CSS lives intheme.css.backendlayout/render.htmlfallback now emits each slot name once. It used to repeat a slot when the same name appeared twice.Docs and skills:
headlessunder "Page types" and adds the bundle to "Feature bundles".docs/features.mdhas a new section, "The forms bundle". It covers the store convention, the spec contract and the caps.docs/architecture.md,docs/api.mdanddocs/harness.mdare updated, plus the README's bundle list.Where this differs from the issue
|, not one per line. The engine refuses a line break in any string-shaped param, textarea included (ruleNoLineBreaks). Without an engine change, an editor-saved option list cannot hold newlines. The template splits on both|and line breaks. Blank options are dropped. A duplicate option produces an HTML comment, not an error.address. In this codebasepermalinkis the stored-link form thatpageparams hold, so it has to stay.addressis what View and the preview read.submitLabelandsuccessMessagearetranslate: true, as well asintro. The Decisions say the texts translate with the page.contact-formelement, so there was nothing to keep untouched.ce-frameand noc-<uid>anchor: they are cells of the form grid on a page with no address. When one form is placed twice on a page,render.htmlprefixes field ids with the placing element's key so ids stay unique.Verification
go build ./...,go vet ./...andgofmt -l .are clean.go test ./...passes with Hugo 0.167.0 on PATH. The Hugo integration tests ran rather than skipping.web:npm run build,tsc --noEmitandnpm testpass (132 files, 3361 tests).internal/theme/forms_test.go):testdata/forms/spec.golden.json).harness add forms/check/removeround trips (cmd/harness/forms_test.go,internal/removecmd/forms_test.go), and placement shows exactly the eight field elements on a form page.harness initsite,harness add formsthenharness checkreportsok.harness serve. The behaviour is covered by the vitest tests on the docheader, the entry editor and the preview pane.🤖 Generated with Claude Code
Engine: page types accept `headless: true`. A page created with such a type, and each translation of it, gets `build: {list: never, render: never}`; its tree node keeps its permalink but has no address and carries `headless: true`; `harness check` warns on a headless page file lacking the build key. content.Apply writes flat string maps. Editor: the View button is disabled with "This page has no address" on a headless page in the Page module and in a module's entry editor. Bundle scaffold/features/forms: the headless `forms` and `form` page types, the Forms module, eight field elements, the `form` element, and the forms/spec.html and forms/render.html partials that return a form's field spec and draw it. Theme: ce/uid.html, ce/bool.html, backendlayout/body.html and backendlayout/links.html now own rules the bundle and the theme share; the forms-form classes live in theme.css. Docs: AGENTS.md (headless, the bundle), docs/features.md, architecture, api, harness, the theme README and the affected skills. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>[autoland] verdict: pass
PASS — landable as-is.
Signal relied on: CI aggregate
success(ci / build-and-test: web build + vitest, go vet, go test with Hugo 0.167.0 installed so the Hugo integration tests in internal/theme/forms_test.go and headless_test.go ran, go build). gofmt checked locally on the changed Go files: clean. Head is current with main (merge-base = main), no conflict.Conventions: Conventional Commits title;
Closes #322present; diff matches #322. The divergences are documented and justified: select options are separated by|because ruleNoLineBreaks (internal/element/fieldtype.go:657) refuses line breaks in textarea params, and a headless node keeps its permalink withaddress: ""because page params store the permalink. The theme refactor (ce/uid.html, ce/bool.html, backendlayout/body.html, backendlayout/links.html) gives each shared rule one owner, as CLAUDE.md requires, and does not restate them. The headless rule has one owner in internal/pagetype/headless.go. The client reads the server'sheadlessverdict. AGENTS.md, docs/features.md, docs/api.md, docs/harness.md, docs/architecture.md, the theme README and the affected skills are updated together.Spot-checked locally: a later front-matter save keeps the
buildmap canonical in YAML and TOML.