feat: page field type with shared page-tree picker; news-list section becomes a picked page reference #86

Merged
dominik.polakovics merged 1 commit from afk/83 into main 2026-07-18 01:24:43 +02:00

What

A ninth engine field type page: a reference to a site page, stored as the page's canonical pretty URL (e.g. /news/) — the same value shape as link, so the existing linkrefs whole-value matching gives rename/move rewriting and delete-warnings with zero linkrefs changes. An optional sidecar key pageType: restricts eligible pages by their effective type (front-matter type, else the section-name convention).

Closes #83

Engine (Go)

  • element: TypePage + Param.PageType (pageType: legal only on type: page, non-empty; strict-YAML via RawParam); ValidateValues stays pure — page values ride the generic string arm.
  • pageops.CheckPageParams (new): the save-time half — exact-permalink lookup via new exported pagetree.Node.FindByPermalink (real pages only; server's duplicate walk deleted), effective-type match via effectiveTypeOf. Messages: must reference an existing page / must reference a page of type "…".
  • Wired into both save paths in their native 422 shapes: handleSavePage ({"errors":[…]}, items rows namespaced items[i].param, tree built per request only when a page param is declared) and pageops.SaveSettings ({"fields":{…}}).
  • Publish-time draft-link warning now also collects page-typed params (they store internal URLs exactly like link params).
  • scaffold/site/AGENTS.md documents the ninth type + pageType:; drift test passes.

Web

  • TreeBrowser extracted from LinkPicker's Internal tab (rows, selection/expansion, pre-fill, loading/error states); LinkPicker composes it — its tests pass byte-unchanged.
  • PagePicker: thin dialog over the shared tree; with pageType set, non-matching pages are visible but disabled (muted, out of tab order) while their chevrons stay live so nested eligible pages remain reachable; footer Choose commits the permalink.
  • PageField: LinkField's chip pattern (Choose…/Edit…/Clear, server-resolved title via resolveLink with raw-URL fallback), no free text, no <name>Target companion. Works in element forms, items rows, page settings, and create dialogs via the shared FieldWidget dispatch.

News bundle

  • news-list.yaml: section is now type: page + pageType: news. The shortcode is untouched — verified empirically against Hugo 0.163.3 (the CI pin): site.GetPage resolves /news/, /news, and legacy hand-typed news alike, and a stale value degrades to an empty list. Vendored sites update by re-running harness add news.

Proof

  • go vet ./... clean; go test ./... fully green with hugo on PATH (hugo-gated theme/build tests included).
  • Web: 40 test files / 792 tests green (baseline 38/778 + 14 new); tsc --noEmit clean.
  • New tests pin every acceptance criterion: sidecar load rules, checker semantics (explicit + implied types), element-save 422 + items namespacing + stored pretty URL, settings-save 422, rename rewriting of stored page values (TestRenameRewritesPageParamValues), picker eligibility/disabled rows/pre-fill, chip round-trip incl. server-422 inline rendering, and the news shortcode's canonical/legacy/stale behavior.

🤖 Generated with Claude Code

## What A ninth engine field type `page`: a reference to a site page, stored as the page's canonical pretty URL (e.g. `/news/`) — the same value shape as `link`, so the existing linkrefs whole-value matching gives rename/move rewriting and delete-warnings with zero linkrefs changes. An optional sidecar key `pageType:` restricts eligible pages by their **effective** type (front-matter `type`, else the section-name convention). Closes #83 ## Engine (Go) - `element`: `TypePage` + `Param.PageType` (`pageType:` legal only on `type: page`, non-empty; strict-YAML via `RawParam`); `ValidateValues` stays pure — page values ride the generic string arm. - `pageops.CheckPageParams` (new): the save-time half — exact-permalink lookup via new exported `pagetree.Node.FindByPermalink` (real pages only; server's duplicate walk deleted), effective-type match via `effectiveTypeOf`. Messages: `must reference an existing page` / `must reference a page of type "…"`. - Wired into **both** save paths in their native 422 shapes: `handleSavePage` (`{"errors":[…]}`, items rows namespaced `items[i].param`, tree built per request only when a page param is declared) and `pageops.SaveSettings` (`{"fields":{…}}`). - Publish-time draft-link warning now also collects page-typed params (they store internal URLs exactly like link params). - `scaffold/site/AGENTS.md` documents the ninth type + `pageType:`; drift test passes. ## Web - `TreeBrowser` extracted from LinkPicker's Internal tab (rows, selection/expansion, pre-fill, loading/error states); LinkPicker composes it — its tests pass **byte-unchanged**. - `PagePicker`: thin dialog over the shared tree; with `pageType` set, non-matching pages are visible but **disabled** (muted, out of tab order) while their chevrons stay live so nested eligible pages remain reachable; footer Choose commits the permalink. - `PageField`: LinkField's chip pattern (Choose…/Edit…/Clear, server-resolved title via `resolveLink` with raw-URL fallback), no free text, no `<name>Target` companion. Works in element forms, items rows, page settings, and create dialogs via the shared `FieldWidget` dispatch. ## News bundle - `news-list.yaml`: `section` is now `type: page` + `pageType: news`. The shortcode is **untouched** — verified empirically against Hugo 0.163.3 (the CI pin): `site.GetPage` resolves `/news/`, `/news`, and legacy hand-typed `news` alike, and a stale value degrades to an empty list. Vendored sites update by re-running `harness add news`. ## Proof - `go vet ./...` clean; `go test ./...` fully green **with hugo on PATH** (hugo-gated theme/build tests included). - Web: 40 test files / 792 tests green (baseline 38/778 + 14 new); `tsc --noEmit` clean. - New tests pin every acceptance criterion: sidecar load rules, checker semantics (explicit + implied types), element-save 422 + items namespacing + stored pretty URL, settings-save 422, rename rewriting of stored page values (`TestRenameRewritesPageParamValues`), picker eligibility/disabled rows/pre-fill, chip round-trip incl. server-422 inline rendering, and the news shortcode's canonical/legacy/stale behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: page field type with shared page-tree picker; news-list section becomes a picked page reference
All checks were successful
ci / build-and-test (pull_request) Successful in 5m37s
956877e437
A ninth engine field type `page` stores a reference to a site page as its
canonical pretty URL (the link value shape), so the existing linkrefs pass
gives rename/move rewriting and delete-warnings for free. An optional
sidecar key `pageType:` restricts eligible pages by effective type; both
save paths (element save incl. items rows, page-settings save) 422 with an
inline field error on a dangling or wrong-type reference. The web editor
gains a shared TreeBrowser extracted from LinkPicker's Internal tab, a thin
PagePicker dialog (non-matching pages visible but disabled), and a PageField
chip widget. news-list's `section` param becomes `page` + `pageType: news`;
the shortcode is untouched — site.GetPage resolves both the canonical
/news/ form and legacy hand-typed paths, and stale values degrade to an
empty list.

Closes #83

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

This was generated by AI while landing a PR.

Landing audit — PR #86main

Verdict: PASS

Verification signal relied on: Forgejo Actions ci / build-and-testsuccess (5m37s, run #75). Not re-run locally. This repo pins hugo in CI, so the hugo-gated theme/build tests (including the new news-shortcode empirical tests) were exercised.

Checks performed:

  • AFK contract: head afk/83, body carries a working Closes #83.
  • Convention: title is Conventional Commits (feat:).
  • AGENTS.md drift (CLAUDE.md rule): the ninth field type page + pageType: are documented in scaffold/site/AGENTS.md; the mechanical drift test covers it.
  • Diff vs issue #83 acceptance criteria — all seven met:
    • Engine accepts type: page with optional pageType:; value stored as canonical pretty URL (schema.go, load.go, doc.go).
    • Save-time 422 for missing/wrong-type page — wired into both save paths (server.go handleSavePage with items[i].param namespacing; pageops/settings.go SaveSettings) via new pageops.CheckPageParams; the fieldErrs merge into the single 422.
    • Shared tree-browse extracted to TreeBrowser.tsx; LinkPicker composes it and keeps its historical test-ids (tests byte-unchanged).
    • PageField chip (Choose…/Edit…/Clear) + PagePicker with non-matching pageType rows visible-but-disabled, chevrons live.
    • news-list.yaml: section is type: page + pageType: news; shortcode untouched.
    • Rename rewriting via existing linkrefs pass, proven by TestRenameRewritesPageParamValues.
    • Softened shortcode criterion resolved empirically: internal/theme/news_test.go pins that site.GetPage resolves the canonical /news/, the legacy news, and degrades to an empty list on a stale /gone/ — so no shortcode normalization was needed.
  • Mergeable: no conflict with main; no resolution required.

Awaiting the human's free-text go-ahead to merge.

> *This was generated by AI while landing a PR.* ## Landing audit — PR #86 → `main` **Verdict: PASS** **Verification signal relied on:** Forgejo Actions `ci / build-and-test` — **success** (5m37s, run #75). Not re-run locally. This repo pins hugo in CI, so the hugo-gated theme/build tests (including the new news-shortcode empirical tests) were exercised. **Checks performed:** - **AFK contract:** head `afk/83`, body carries a working `Closes #83`. ✅ - **Convention:** title is Conventional Commits (`feat:`). ✅ - **AGENTS.md drift (CLAUDE.md rule):** the ninth field type `page` + `pageType:` are documented in `scaffold/site/AGENTS.md`; the mechanical drift test covers it. ✅ - **Diff vs issue #83 acceptance criteria** — all seven met: - Engine accepts `type: page` with optional `pageType:`; value stored as canonical pretty URL (`schema.go`, `load.go`, `doc.go`). ✅ - Save-time 422 for missing/wrong-type page — wired into **both** save paths (`server.go` handleSavePage with `items[i].param` namespacing; `pageops/settings.go` SaveSettings) via new `pageops.CheckPageParams`; the `fieldErrs` merge into the single 422. ✅ - Shared tree-browse extracted to `TreeBrowser.tsx`; `LinkPicker` composes it and keeps its historical test-ids (tests byte-unchanged). ✅ - `PageField` chip (Choose…/Edit…/Clear) + `PagePicker` with non-matching `pageType` rows visible-but-disabled, chevrons live. ✅ - `news-list.yaml`: `section` is `type: page` + `pageType: news`; shortcode untouched. ✅ - Rename rewriting via existing linkrefs pass, proven by `TestRenameRewritesPageParamValues`. ✅ - **Softened shortcode criterion resolved empirically:** `internal/theme/news_test.go` pins that `site.GetPage` resolves the canonical `/news/`, the legacy `news`, and degrades to an empty list on a stale `/gone/` — so no shortcode normalization was needed. ✅ - **Mergeable:** no conflict with `main`; no resolution required. Awaiting the human's free-text go-ahead to merge.
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!86
No description provided.