New page field type with shared page-tree picker; news-list section becomes a picked page reference #83

Closed
opened 2026-07-17 23:28:46 +02:00 by dominik.polakovics · 2 comments

What to build

A ninth engine field type page: a reference to a site page, stored as the canonical pretty URL (/news/) — the same value shape as link, so the existing linkrefs whole-value matching gives rename/move rewriting and delete-warnings for free (zero linkrefs changes). An optional sidecar key pageType: restricts selection: the picker shows the whole tree with non-matching pages visible but disabled. The server validates on save: 422 with an inline field error if the referenced page does not exist or is not the declared pageType. No implicit Target companion param.

UI: extract the tree-browse from LinkPicker's Internal tab into a shared component; LinkPicker composes it, and a new thin page-picker dialog wraps it for the page field. The field widget follows the LinkField chip pattern (Choose… / Edit… / Clear, server-resolved label), no free-text path input.

News bundle: news-list.yaml flips section from text to page with pageType: news. The shortcode is unchanged, so existing hand-typed values like news keep resolving; already-vendored sites update by re-running harness add news (no migration machinery).

Key code: internal/element/schema.go (FieldType), web/src/components/LinkPicker.tsx, web/src/components/fields.tsx, scaffold/features/news/editor/elements/news-list.yaml, scaffold/site/AGENTS.md, internal/element/agentsdoc_test.go (drift test).

Acceptance criteria

  • Engine accepts field type page (element params and page-type fields) with optional pageType: restriction; value stored as canonical pretty URL
  • Save-time validation: 422 inline error when the referenced page is missing or of the wrong pageType; rendering still degrades to an empty list on a stale value
  • Shared tree-browse component extracted from LinkPicker's Internal tab; LinkPicker behavior unchanged
  • page field widget: chip with Choose…/Edit…/Clear opening the page-picker dialog; non-matching page types visible but disabled when pageType: is set
  • news-list.yaml: section is page + pageType: news; shortcode untouched; old plain-path values still render
  • Renaming/moving a page rewrites stored page values via existing linkrefs pass (test proves it)
  • scaffold/site/AGENTS.md documents the page type and pageType:; drift test passes

Blocked by

  • #81 — both changes add a key to the shared RawParam/element.Param sidecar schema (internal/element/load.go, internal/element/schema.go) and both edit scaffold/site/AGENTS.md; serialized to avoid conflicting parallel edits (no functional dependency)
## What to build A ninth engine field type `page`: a reference to a site page, stored as the canonical pretty URL (`/news/`) — the same value shape as `link`, so the existing `linkrefs` whole-value matching gives rename/move rewriting and delete-warnings for free (zero linkrefs changes). An optional sidecar key `pageType:` restricts selection: the picker shows the whole tree with non-matching pages visible but disabled. The server validates on save: 422 with an inline field error if the referenced page does not exist or is not the declared `pageType`. No implicit `Target` companion param. UI: extract the tree-browse from `LinkPicker`'s Internal tab into a shared component; `LinkPicker` composes it, and a new thin page-picker dialog wraps it for the `page` field. The field widget follows the `LinkField` chip pattern (Choose… / Edit… / Clear, server-resolved label), no free-text path input. News bundle: `news-list.yaml` flips `section` from `text` to `page` with `pageType: news`. The shortcode is unchanged, so existing hand-typed values like `news` keep resolving; already-vendored sites update by re-running `harness add news` (no migration machinery). Key code: `internal/element/schema.go` (`FieldType`), `web/src/components/LinkPicker.tsx`, `web/src/components/fields.tsx`, `scaffold/features/news/editor/elements/news-list.yaml`, `scaffold/site/AGENTS.md`, `internal/element/agentsdoc_test.go` (drift test). ## Acceptance criteria - [ ] Engine accepts field type `page` (element params and page-type fields) with optional `pageType:` restriction; value stored as canonical pretty URL - [ ] Save-time validation: 422 inline error when the referenced page is missing or of the wrong `pageType`; rendering still degrades to an empty list on a stale value - [ ] Shared tree-browse component extracted from `LinkPicker`'s Internal tab; `LinkPicker` behavior unchanged - [ ] `page` field widget: chip with Choose…/Edit…/Clear opening the page-picker dialog; non-matching page types visible but disabled when `pageType:` is set - [ ] `news-list.yaml`: `section` is `page` + `pageType: news`; shortcode untouched; old plain-path values still render - [ ] Renaming/moving a page rewrites stored `page` values via existing linkrefs pass (test proves it) - [ ] `scaffold/site/AGENTS.md` documents the `page` type and `pageType:`; drift test passes ## Blocked by - #81 — both changes add a key to the shared `RawParam`/`element.Param` sidecar schema (`internal/element/load.go`, `internal/element/schema.go`) and both edit `scaffold/site/AGENTS.md`; serialized to avoid conflicting parallel edits (no functional dependency)
Author
Owner

This was generated by AI during triage.

Triage addendum — verified against the codebase

The brief's core premise checks out; one acceptance criterion is softened below. Facts for the implementing agent:

Confirmed:

  • Exactly eight field types today, in validFieldTypes (internal/element/schema.go ~33–42). Page-type fields reuse the same vocabulary via element.ParseParams, so adding page there covers element params and page-type fields at once. The drift test (internal/element/agentsdoc_test.go) mechanically forces the new type to be named in scaffold/site/AGENTS.md.
  • The linkrefs claim genuinely holds: internal/linkrefs/linkrefs.go does whole-value prefix matching over every string shortcode param and front-matter value — it never enumerates field types. A page value stored as a canonical pretty URL gets rename/move rewriting (rewriteValue, via internal/pageops/relocate.go) and delete-warnings (References, via internal/server/linktargets.go) with zero linkrefs changes.
  • The pageType: restriction key is new (add to RawParam in internal/element/load.go + element.Param — both sidecar loaders are strict-YAML). A page's type for validation = front-matter type, else the section-name convention — see effectiveType in internal/pagetree/build.go (~382–421).
  • 422-with-inline-field-errors pattern to follow: handlePageSettingsSave in internal/server/pageops.go (~364–405) and element.FieldError / Schema.ValidateValues in internal/element/values.go.
  • Picker extraction: LinkTreeRow in web/src/components/LinkPicker.tsx (~139–199) is already standalone behind the LinkTreeOps interface; what needs lifting into the shared component is the surrounding tree state (selected/expanded signals, fetchTree resource, findChain, the pre-fill effect). The right-pane commit rows and tab machinery stay LinkPicker-specific. LinkField (web/src/components/fields.tsx ~518–637) is the chip pattern to mirror — note it resolves labels via resolveLink, non-blocking with raw-href fallback.
  • No implicit companion: the <name>Target reservation is gated on Type == TypeLink (schema.go ~207–217), so page simply stays out of it.

Softened criterion — shortcode: the brief says the shortcode is untouched, but news-list.html resolves via site.GetPage (.Get "section"), which takes a Hugo content path (news, /news). Whether it also accepts the stored canonical pretty URL /news/ is unverified — nothing normalizes the value. Verify empirically (render a site with a /news/-form value); if GetPage doesn't resolve it, a minimal normalization inside the shortcode (e.g. trimming the trailing slash before GetPage) is in scope. Old hand-typed plain-path values must keep resolving either way, and rendering must still degrade to an empty list on a stale value.

Coordination note: #81 also adds a key to RawParam/element.Param and touches AGENTS.md — expect a small rebase if both are in flight simultaneously.

> *This was generated by AI during triage.* ## Triage addendum — verified against the codebase The brief's core premise checks out; one acceptance criterion is softened below. Facts for the implementing agent: **Confirmed:** - Exactly eight field types today, in `validFieldTypes` (`internal/element/schema.go` ~33–42). Page-type fields reuse the same vocabulary via `element.ParseParams`, so adding `page` there covers element params and page-type fields at once. The drift test (`internal/element/agentsdoc_test.go`) mechanically forces the new type to be named in `scaffold/site/AGENTS.md`. - The linkrefs claim genuinely holds: `internal/linkrefs/linkrefs.go` does whole-value prefix matching over **every** string shortcode param and front-matter value — it never enumerates field types. A `page` value stored as a canonical pretty URL gets rename/move rewriting (`rewriteValue`, via `internal/pageops/relocate.go`) and delete-warnings (`References`, via `internal/server/linktargets.go`) with zero linkrefs changes. - The `pageType:` restriction key is new (add to `RawParam` in `internal/element/load.go` + `element.Param` — both sidecar loaders are strict-YAML). A page's type for validation = front-matter `type`, else the section-name convention — see `effectiveType` in `internal/pagetree/build.go` (~382–421). - 422-with-inline-field-errors pattern to follow: `handlePageSettingsSave` in `internal/server/pageops.go` (~364–405) and `element.FieldError` / `Schema.ValidateValues` in `internal/element/values.go`. - Picker extraction: `LinkTreeRow` in `web/src/components/LinkPicker.tsx` (~139–199) is already standalone behind the `LinkTreeOps` interface; what needs lifting into the shared component is the surrounding tree state (`selected`/`expanded` signals, `fetchTree` resource, `findChain`, the pre-fill effect). The right-pane commit rows and tab machinery stay LinkPicker-specific. `LinkField` (`web/src/components/fields.tsx` ~518–637) is the chip pattern to mirror — note it resolves labels via `resolveLink`, non-blocking with raw-href fallback. - No implicit companion: the `<name>Target` reservation is gated on `Type == TypeLink` (`schema.go` ~207–217), so `page` simply stays out of it. **Softened criterion — shortcode:** the brief says the shortcode is untouched, but `news-list.html` resolves via `site.GetPage (.Get "section")`, which takes a Hugo content path (`news`, `/news`). Whether it also accepts the stored canonical pretty URL `/news/` is unverified — nothing normalizes the value. **Verify empirically** (render a site with a `/news/`-form value); if `GetPage` doesn't resolve it, a minimal normalization inside the shortcode (e.g. trimming the trailing slash before `GetPage`) is **in scope**. Old hand-typed plain-path values must keep resolving either way, and rendering must still degrade to an empty list on a stale value. **Coordination note:** #81 also adds a key to `RawParam`/`element.Param` and touches `AGENTS.md` — expect a small rebase if both are in flight simultaneously.
Author
Owner

This was generated by AI during triage.

Blocked by

  • #81 — both changes add a key to the shared RawParam/element.Param sidecar schema (internal/element/load.go, internal/element/schema.go) and both edit scaffold/site/AGENTS.md. Serializing behind #81 avoids a conflicting parallel edit; there is no functional dependency.

(Supersedes the "Blocked by: None" section in the issue body, which cannot be edited via the tracker CLI.)

> *This was generated by AI during triage.* ## Blocked by - #81 — both changes add a key to the shared `RawParam`/`element.Param` sidecar schema (`internal/element/load.go`, `internal/element/schema.go`) and both edit `scaffold/site/AGENTS.md`. Serializing behind #81 avoids a conflicting parallel edit; there is no functional dependency. *(Supersedes the "Blocked by: None" section in the issue body, which cannot be edited via the tracker CLI.)*
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#83
No description provided.