feat(news): categories field on news items + category filter on news-list #134

Merged
dominik.polakovics merged 1 commit from afk/130 into main 2026-07-19 19:52:15 +02:00

Closes #130

Final slice of the News v2 categories stack (#77#128#129 → this): categories become editor-visible on news items and filterable on the news-list element.

What changed

Sidecars (the curation model from #77: vocabulary curated in sidecar options)

  • news-item page type: new categories field, type: multiselect, starter vocabulary [announcements, company, events, product]. The field key is exactly the taxonomy key, so the front-matter list the editor writes feeds the Hugo taxonomy directly.
  • news-list element: new optional category param, type: select, same vocabulary. Single-valued by design (element params are shortcode scalars). Optional + defaultless, so the editor renders a leading empty option and omits the attribute entirely when unset.
  • The vocabulary is deliberately duplicated between the two sidecars; each carries a comment pointing at the other, and TestNewsBundleSidecarsLoad now asserts the two options lists are identical — the lockstep is mechanically enforced, not just promised.

Templates

  • news-list shortcode: pipeline is now children → type → non-draft → optional where … "Params.categories" "intersect" → date desc → first limit, so the limit caps the filtered count. Unset category takes the exact pre-existing path (whitespace-identical output).
  • Item detail + shared teaser cards (section list and term pages): each item's categories render as links to /categories/<term>/. Uncategorized items render no list at all.
  • RSS untouched.

Docs: harness add news next-steps text no longer claims the editor field "ships separately"; the AGENTS.md feature-bundle paragraph describes the field, the filter param, and the by-hand lockstep.

Tests

  • Sidecar shape + vocabulary-lockstep assertions in TestNewsBundleSidecarsLoad.
  • Two new fixture pages drive the filter through a real Hugo build: category="company" limit=2 proves limit-after-filter (3 company items, oldest cut) and non-matching-but-newer exclusion; category="product" limit=20 proves exactly the 11 carriers render, excluding the draft and the uncategorized future item. Draft exclusion re-checked under --buildDrafts.
  • Category-link assertions on detail, section list, and term pages; the untouched home/updates fixtures pin the unset-category path.

Full go test ./... and the 973-test web suite are green.

Verified end-to-end

Beyond tests: scaffolded a fresh site, ran the real harness add news, drove the editor in Chromium (Playwright) — toggled the Categories chips on an item and saved (categories = ["company", "product"] landed as a native TOML list), set Category on a news-list element (category="company" landed in the shortcode) — and observed the live Hugo preview: filtered listing, term links on detail/list/term pages, empty-but-valid output for vocabulary-without-items and hand-typed bogus categories, section RSS unchanged, no term feeds.

While verifying I hit a pre-existing Page Settings datetime validation bug (unrelated to this change) — filed as #133.

🤖 Generated with Claude Code

Closes #130 Final slice of the News v2 categories stack (#77 → #128 → #129 → this): categories become editor-visible on news items and filterable on the news-list element. ## What changed **Sidecars** (the curation model from #77: vocabulary curated in sidecar options) - `news-item` page type: new `categories` field, `type: multiselect`, starter vocabulary `[announcements, company, events, product]`. The field key is exactly the taxonomy key, so the front-matter list the editor writes feeds the Hugo taxonomy directly. - `news-list` element: new optional `category` param, `type: select`, same vocabulary. Single-valued by design (element params are shortcode scalars). Optional + defaultless, so the editor renders a leading empty option and omits the attribute entirely when unset. - The vocabulary is deliberately duplicated between the two sidecars; each carries a comment pointing at the other, and `TestNewsBundleSidecarsLoad` now asserts the two options lists are identical — the lockstep is mechanically enforced, not just promised. **Templates** - news-list shortcode: pipeline is now children → type → non-draft → optional `where … "Params.categories" "intersect"` → date desc → `first limit`, so the limit caps the *filtered* count. Unset category takes the exact pre-existing path (whitespace-identical output). - Item detail + shared teaser cards (section list and term pages): each item's categories render as links to `/categories/<term>/`. Uncategorized items render no list at all. - RSS untouched. **Docs**: `harness add news` next-steps text no longer claims the editor field "ships separately"; the AGENTS.md feature-bundle paragraph describes the field, the filter param, and the by-hand lockstep. ## Tests - Sidecar shape + vocabulary-lockstep assertions in `TestNewsBundleSidecarsLoad`. - Two new fixture pages drive the filter through a real Hugo build: `category="company" limit=2` proves limit-after-filter (3 company items, oldest cut) and non-matching-but-newer exclusion; `category="product" limit=20` proves exactly the 11 carriers render, excluding the draft and the uncategorized future item. Draft exclusion re-checked under `--buildDrafts`. - Category-link assertions on detail, section list, and term pages; the untouched home/updates fixtures pin the unset-category path. Full `go test ./...` and the 973-test web suite are green. ## Verified end-to-end Beyond tests: scaffolded a fresh site, ran the real `harness add news`, drove the editor in Chromium (Playwright) — toggled the Categories chips on an item and saved (`categories = ["company", "product"]` landed as a native TOML list), set Category on a news-list element (`category="company"` landed in the shortcode) — and observed the live Hugo preview: filtered listing, term links on detail/list/term pages, empty-but-valid output for vocabulary-without-items and hand-typed bogus categories, section RSS unchanged, no term feeds. While verifying I hit a pre-existing Page Settings datetime validation bug (unrelated to this change) — filed as #133. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(news): categories field on news items + category filter on news-list
All checks were successful
ci / build-and-test (pull_request) Successful in 6m23s
ce92f03203
Wire categories into the news bundle, on top of the multiselect field
type (#128) and the taxonomy/term-page enablement (#129):

- news-item page type gains a curated `categories` multiselect field
  (starter vocabulary in the sidecar's options; the field key is the
  taxonomy key, so saved values feed Hugo's categories taxonomy
  directly)
- the news-list element gains an optional single-valued `category`
  select param; the shortcode narrows items to that category before
  applying the limit, unset behavior unchanged
- the item detail template and the shared teaser cards (section list +
  term pages) link each item's categories to its /categories/<term>/
  page
- the vocabulary is deliberately duplicated between the two sidecars;
  both carry cross-referencing comments, and the sidecar-load test now
  mechanically enforces the lockstep
- theme tests cover the filtered listing (limit-after-filter, draft and
  uncategorized exclusion, also under --buildDrafts) and the category
  links; `harness add news` next-steps text no longer claims the
  editor field ships separately

Closes #130

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

This was generated by AI while landing a PR.

Landing audit — PASS

Verification signal relied on: Forgejo Actions ci / build-and-testsuccess (6m23s). The workflow is unconditional on pull_request and runs the frontend build + npm test, then go vet ./..., go test ./..., go build with the pinned Hugo extended v0.163.3. The theme-build fixtures that carry this PR's new assertions run inside that go test, so the green light genuinely exercised the changed path. Not re-run locally.

Checks performed

  • Mergeability — merges cleanly into main (no conflicts).
  • Conventions — title feat(news): … is Conventional Commits; body carries a working Closes #130.
  • AGENTS.md drift rule (CLAUDE.md) — the change alters page-type sidecar fields and element params, so scaffold/site/AGENTS.md had to move; it does, describing the categories multiselect field, the category filter param, and the hand-maintained lockstep.
  • Diff vs. issue #130 intent — all acceptance criteria are met and nothing strays into the stated out-of-scope (no shared vocabulary source, no multi-category filter, no tags, RSS untouched).

Notes from the diff read (non-blocking)

  • The shortcode's unset-category path is whitespace-identical to the previous single-line pipeline — every new action opens with {{-, so the emitted text is unchanged, and the untouched home/updates fixtures pin that.
  • $items = … (assignment, not :=) inside the with $category block correctly mutates the outer variable rather than shadowing it.
  • The filter runs before first $limit, so the limit caps the filtered count; the company-news fixture (3 matches, limit=2) is a real proof of that ordering, not a tautology.
  • Vocabulary duplication across the two sidecars is deliberate per the #77 decision record, and TestNewsBundleSidecarsLoad now asserts the two options lists are equal — the promise is mechanically enforced.

Verdict: PASS. No blockers, no concerns. Awaiting the human's merge confirmation.

> *This was generated by AI while landing a PR.* ## Landing audit — PASS **Verification signal relied on:** Forgejo Actions `ci / build-and-test` — **success** (6m23s). The workflow is unconditional on `pull_request` and runs the frontend build + `npm test`, then `go vet ./...`, `go test ./...`, `go build` with the pinned Hugo extended v0.163.3. The theme-build fixtures that carry this PR's new assertions run inside that `go test`, so the green light genuinely exercised the changed path. Not re-run locally. **Checks performed** - **Mergeability** — merges cleanly into `main` (no conflicts). - **Conventions** — title `feat(news): …` is Conventional Commits; body carries a working `Closes #130`. - **AGENTS.md drift rule (CLAUDE.md)** — the change alters page-type sidecar fields and element params, so `scaffold/site/AGENTS.md` had to move; it does, describing the `categories` multiselect field, the `category` filter param, and the hand-maintained lockstep. - **Diff vs. issue #130 intent** — all acceptance criteria are met and nothing strays into the stated out-of-scope (no shared vocabulary source, no multi-category filter, no tags, RSS untouched). **Notes from the diff read (non-blocking)** - The shortcode's unset-`category` path is whitespace-identical to the previous single-line pipeline — every new action opens with `{{-`, so the emitted text is unchanged, and the untouched home/updates fixtures pin that. - `$items = …` (assignment, not `:=`) inside the `with $category` block correctly mutates the outer variable rather than shadowing it. - The filter runs before `first $limit`, so the limit caps the filtered count; the `company-news` fixture (3 matches, `limit=2`) is a real proof of that ordering, not a tautology. - Vocabulary duplication across the two sidecars is deliberate per the #77 decision record, and `TestNewsBundleSidecarsLoad` now asserts the two options lists are equal — the promise is mechanically enforced. **Verdict: PASS.** No blockers, no concerns. Awaiting the human's merge confirmation.
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!134
No description provided.