feat: page-teasers section becomes a picked page reference #98

Merged
dominik.polakovics merged 1 commit from afk/89 into main 2026-07-18 03:59:33 +02:00

Upgrades the scaffolded page-teasers element's section param from type: text to type: page, so editors pick the section from the shared page-tree picker (the news-list pattern from #86) and the stored value is the page's canonical pretty URL. Per the issue, no pageType: restriction — any section-like page may be teased.

What changed

  • scaffold/editor/elements/page-teasers.yamlsection is now type: page, required: true, label "Section" (was "Section path"); header comment rewritten in news-list's style.
  • internal/pageops/pagerefs.go — text-era content stores hand-typed paths like /posts, but canonical permalinks carry a trailing slash (/posts/) and CheckPageParams matched exactly, so the flip alone would 422 every untouched legacy value on re-save. The validator now tolerates exactly one deviation: a value missing only the trailing slash is retried with it appended, then flows through the existing pageType check unchanged. Dangling paths, wrong-type pages, and any other legacy shape (e.g. posts without the leading slash) are still rejected. FindByPermalink itself stays exact — its other callers rely on that.
  • scaffold/themes/deckle/layouts/shortcodes/page-teasers.html — comment-only update; every line of template logic is untouched, so shortcode output is byte-identical for the same stored value (Hugo's site.GetPage already resolves both forms).
  • scaffold/site/AGENTS.md — the page field-type paragraph documents the trailing-slash grace (blueprint-tracks-engine rule; agentsdoc drift test green).
  • Testspagerefs_test.go: legacy form passes, dangling fails in both forms, grace is exactly the trailing slash, pageType enforced on tolerated values. theme_test.go + new fixture testdata/library-site/content/teasers-canonical/: the home page (legacy section="/posts") renders exactly the non-draft posts, and a page storing canonical section="/posts/" renders a byte-equal teaser list.

Acceptance criteria → proof

  • Sidecar declares type: page, required, label in spirit → yaml diff
  • Picker in the Page module form → schema-driven (fields.tsx dispatches on param.type === "page" since #86), no web change needed
  • Pre-existing stored /posts loads, validates, publishes unchanged → validator grace + untouched shortcode; library-site fixture keeps the legacy form and its build output is asserted
  • CheckPageParams passes valid sections, rejects dangling → new unit tests
  • Byte-identical shortcode output → comment-only template diff + byte-equal list assertion across legacy/canonical fixtures

Verification

go vet ./... clean; go test -count=1 ./... — 21 packages ok (hugo in PATH, no skips; TestPageTeasersSectionForms ran); web vitest 792/792.

Noted, not changed: internal/server/linktargets.go resolves link values with the exact matcher — correct to leave, since link values were always picker-written canonical URLs.

Closes #89

🤖 Generated with Claude Code

Upgrades the scaffolded `page-teasers` element's `section` param from `type: text` to `type: page`, so editors pick the section from the shared page-tree picker (the news-list pattern from #86) and the stored value is the page's canonical pretty URL. Per the issue, no `pageType:` restriction — any section-like page may be teased. ## What changed - **`scaffold/editor/elements/page-teasers.yaml`** — `section` is now `type: page`, `required: true`, label "Section" (was "Section path"); header comment rewritten in news-list's style. - **`internal/pageops/pagerefs.go`** — text-era content stores hand-typed paths like `/posts`, but canonical permalinks carry a trailing slash (`/posts/`) and `CheckPageParams` matched exactly, so the flip alone would 422 every untouched legacy value on re-save. The validator now tolerates exactly one deviation: a value missing only the trailing slash is retried with it appended, then flows through the existing pageType check unchanged. Dangling paths, wrong-type pages, and any other legacy shape (e.g. `posts` without the leading slash) are still rejected. `FindByPermalink` itself stays exact — its other callers rely on that. - **`scaffold/themes/deckle/layouts/shortcodes/page-teasers.html`** — comment-only update; every line of template logic is untouched, so shortcode output is byte-identical for the same stored value (Hugo's `site.GetPage` already resolves both forms). - **`scaffold/site/AGENTS.md`** — the `page` field-type paragraph documents the trailing-slash grace (blueprint-tracks-engine rule; agentsdoc drift test green). - **Tests** — `pagerefs_test.go`: legacy form passes, dangling fails in both forms, grace is exactly the trailing slash, pageType enforced on tolerated values. `theme_test.go` + new fixture `testdata/library-site/content/teasers-canonical/`: the home page (legacy `section="/posts"`) renders exactly the non-draft posts, and a page storing canonical `section="/posts/"` renders a byte-equal teaser list. ## Acceptance criteria → proof - Sidecar declares `type: page`, required, label in spirit → yaml diff - Picker in the Page module form → schema-driven (`fields.tsx` dispatches on `param.type === "page"` since #86), no web change needed - Pre-existing stored `/posts` loads, validates, publishes unchanged → validator grace + untouched shortcode; library-site fixture keeps the legacy form and its build output is asserted - `CheckPageParams` passes valid sections, rejects dangling → new unit tests - Byte-identical shortcode output → comment-only template diff + byte-equal list assertion across legacy/canonical fixtures ## Verification `go vet ./...` clean; `go test -count=1 ./...` — 21 packages ok (hugo in PATH, no skips; `TestPageTeasersSectionForms` ran); web `vitest` 792/792. Noted, not changed: `internal/server/linktargets.go` resolves `link` values with the exact matcher — correct to leave, since link values were always picker-written canonical URLs. Closes #89 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: page-teasers section becomes a picked page reference
All checks were successful
ci / build-and-test (pull_request) Successful in 5m53s
b010d77c0f
The scaffolded page-teasers element's `section` param flips from `type:
text` to `type: page`: editors pick the section in the shared page-tree
picker (the news-list pattern from #86) instead of typing a raw path. No
`pageType:` restriction — any section-like page may be teased.

Text-era content stored hand-typed paths like "/posts", while the picker
stores canonical pretty URLs ("/posts/") and save-time validation matched
permalinks exactly — so the flip alone would 422 every untouched legacy
value. CheckPageParams now tolerates exactly one deviation: a value
missing only the trailing slash resolves as if it carried one, then flows
through the existing pageType check. Anything else that names no existing
page is still rejected, and FindByPermalink itself stays exact for its
other callers. Hugo's site.GetPage already resolves both forms, so the
shortcode template is untouched and its output byte-identical; a theme
test pins the legacy and canonical stored forms to the same rendered
teaser list, drafts excluded. AGENTS.md documents the grace per the
blueprint-tracks-engine rule.

Closes #89

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

This was generated by AI while landing a PR.

Landing audit — #98 feat: page-teasers section becomes a picked page reference

Verdict: PASS.

  • Verification signal relied on: Forgejo Actions CI ci / build-and-testsuccess (run #79, 5m53s). Not re-run.
  • Conventional Commits: title feat:
  • AFK contract: head afk/89, body carries a valid Closes #89
  • Merge state: merge-tree against main reports no conflict (branch is behind main but merges cleanly).
  • Diff review vs issue #89 intent:
    • page-teasers.yaml: section flipped textpage, required kept, label "Section path"→"Section" ✓
    • pagerefs.go: trailing-slash grace is narrowly scoped — retried only on exact-match miss, single / append, still enforces the pageType check; FindByPermalink stays exact for other callers ✓
    • shortcode .html: comment-only, template logic byte-identical ✓
    • AGENTS.md: documents the grace (blueprint-tracks-engine rule; agentsdoc drift test green in CI) ✓
    • Tests cover legacy /posts accepted, dangling rejected both forms, grace limited to trailing slash, pageType enforced, byte-equal fixture render.

All five acceptance criteria addressed. Awaiting free-text merge go-ahead.

> *This was generated by AI while landing a PR.* **Landing audit — #98 `feat: page-teasers section becomes a picked page reference`** **Verdict: PASS.** - **Verification signal relied on:** Forgejo Actions CI `ci / build-and-test` — **success** (run #79, 5m53s). Not re-run. - **Conventional Commits:** title `feat:` ✓ - **AFK contract:** head `afk/89`, body carries a valid `Closes #89` ✓ - **Merge state:** `merge-tree` against `main` reports **no conflict** (branch is behind main but merges cleanly). - **Diff review vs issue #89 intent:** - `page-teasers.yaml`: `section` flipped `text`→`page`, `required` kept, label "Section path"→"Section" ✓ - `pagerefs.go`: trailing-slash grace is narrowly scoped — retried only on exact-match miss, single `/` append, still enforces the pageType check; `FindByPermalink` stays exact for other callers ✓ - shortcode `.html`: comment-only, template logic byte-identical ✓ - `AGENTS.md`: documents the grace (blueprint-tracks-engine rule; agentsdoc drift test green in CI) ✓ - Tests cover legacy `/posts` accepted, dangling rejected both forms, grace limited to trailing slash, pageType enforced, byte-equal fixture render. All five acceptance criteria addressed. Awaiting free-text merge go-ahead.
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!98
No description provided.