refactor: define Hugo's effective-page-type rule once in internal/pagepath #122

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

Closes #115

Hugo's effective-page-type rule ("explicit front-matter type when non-empty, else the page's top-level section name") existed three times: pageops.effectiveTypeOf/impliedType, a byte-for-byte local copy in pagetree (fossilized because pageops imports pagetree, not the reverse), and the web client's effectiveNodeType/impliedPageType.

What changed

  • New stdlib-only leaf package internal/pagepath owning the pure rule: ImpliedType(pagePath) and EffectiveType(pagePath, explicitType). The package doc records the full semantics, including the leaf-bundle case (posts/foo/index.mdposts, foo/index.md at root → "") and the content-root case (_index.md"").
  • internal/pageops deletes its private impliedType; effectiveTypeOf is now a thin front-matter-reading wrapper that extracts the explicit type (empty on any read/decode/cast failure — permissive as before) and delegates. The exported pageops.EffectiveType(root, page) signature and behavior are unchanged, so the HTTP layer needed no edits.
  • internal/pagetree deletes its local effectiveType/impliedType copies and their "mirrors … byte for byte" comments; the one call site delegates, keeping the governs-the-node's-children insight as a call-site comment.
  • Web mirror stays (it cannot import Go) but is now pinned by a shared conformance fixture, internal/pagepath/testdata/effective_type_cases.json (12 cases), consumed by BOTH the Go test (pagepath_test.go) and the web unit test (pagemanage.test.ts). Verified both directions: mutating an expected value in the fixture fails the Go suite and the web suite on exactly that case; restored, both are green.

No behavior change anywhere — pure consolidation. internal/pagetype's no-resolution boundary and scaffold/site/AGENTS.md are untouched (out of scope per the issue).

Verification

  • go vet ./... clean; gofmt clean on all touched files
  • go test ./... (with hugo on PATH so the skip-prone suites run): all pass, including the new internal/pagepath conformance test
  • Web: vitest run 947/947 pass (12 new conformance tests); tsc --noEmit clean
  • Fixture-coupling proof: one mutated expectation → 1 Go subtest + 1 web test fail, same case name in both

🤖 Generated with Claude Code

Closes #115 Hugo's effective-page-type rule ("explicit front-matter `type` when non-empty, else the page's top-level section name") existed three times: `pageops.effectiveTypeOf`/`impliedType`, a byte-for-byte local copy in `pagetree` (fossilized because pageops imports pagetree, not the reverse), and the web client's `effectiveNodeType`/`impliedPageType`. ## What changed - **New stdlib-only leaf package `internal/pagepath`** owning the pure rule: `ImpliedType(pagePath)` and `EffectiveType(pagePath, explicitType)`. The package doc records the full semantics, including the leaf-bundle case (`posts/foo/index.md` → `posts`, `foo/index.md` at root → `""`) and the content-root case (`_index.md` → `""`). - **`internal/pageops`** deletes its private `impliedType`; `effectiveTypeOf` is now a thin front-matter-reading wrapper that extracts the explicit type (empty on any read/decode/cast failure — permissive as before) and delegates. The exported `pageops.EffectiveType(root, page)` signature and behavior are unchanged, so the HTTP layer needed no edits. - **`internal/pagetree`** deletes its local `effectiveType`/`impliedType` copies and their "mirrors … byte for byte" comments; the one call site delegates, keeping the governs-the-node's-children insight as a call-site comment. - **Web mirror stays** (it cannot import Go) but is now pinned by a shared conformance fixture, `internal/pagepath/testdata/effective_type_cases.json` (12 cases), consumed by BOTH the Go test (`pagepath_test.go`) and the web unit test (`pagemanage.test.ts`). Verified both directions: mutating an expected value in the fixture fails the Go suite and the web suite on exactly that case; restored, both are green. No behavior change anywhere — pure consolidation. `internal/pagetype`'s no-resolution boundary and `scaffold/site/AGENTS.md` are untouched (out of scope per the issue). ## Verification - `go vet ./...` clean; `gofmt` clean on all touched files - `go test ./...` (with hugo on PATH so the skip-prone suites run): all pass, including the new `internal/pagepath` conformance test - Web: `vitest run` 947/947 pass (12 new conformance tests); `tsc --noEmit` clean - Fixture-coupling proof: one mutated expectation → 1 Go subtest + 1 web test fail, same case name in both 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor: define Hugo's effective-page-type rule once in internal/pagepath
All checks were successful
ci / build-and-test (pull_request) Successful in 6m2s
b52f42eadc
The explicit-front-matter-type-else-section-name-fallback rule existed
three times: pageops.effectiveTypeOf/impliedType, a byte-for-byte local
copy in pagetree (fossilized by the import direction), and the web
client's effectiveNodeType/impliedPageType mirror.

Move the pure rule into a new stdlib-only leaf package, internal/pagepath
(ImpliedType, EffectiveType), and delegate both Go call sites to it.
pageops.EffectiveType keeps its exported signature and permissive
file-reading behavior; pagetree's local copies and the "mirrors … byte
for byte" comments are gone. No behavior change.

The web mirror stays (it cannot import Go) but is now pinned by a shared
conformance fixture, internal/pagepath/testdata/effective_type_cases.json,
consumed by both the Go test and the web unit test — editing an expected
value fails both suites, so a divergence breaks a test instead of hiding
in a comment.

Closes #115

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

This was generated by AI while landing a PR.

Landing audit — PASS, merged

Verification signal relied on: Forgejo Actions ci / build-and-test (pull_request)success in 6m2s. Not re-run locally; the repo's own CI already exercised this diff.

Checked:

  • Title is Conventional Commits (refactor:).
  • AFK contract: head afk/115 carries a working Closes #115.
  • Merged clean into main (no conflict, no resolution commit).
  • Diff reviewed against issue #115's acceptance criteria — all six met.

Review notes:

  • internal/pagepath is genuinely stdlib-only (path, strings), satisfying the leaf-package constraint.
  • The pageops.effectiveTypeOf rewrite is behavior-preserving: the old code returned s only when castErr == nil && s != "", else fell through to impliedType; the new code assigns explicit = s on a clean cast and lets pagepath.EffectiveType apply the same != "" test. Identical on every path, including the empty-string-type case.
  • Both Go duplicates deleted; path/strings remain used in pagetree/build.go and pageops/paths.go, so no dangling imports.
  • The shared fixture is real coupling: the Go test and the web test read the same internal/pagepath/testdata/effective_type_cases.json.
  • The "mirrors … byte for byte" comments are gone; the governs-the-children insight survives as a call-site comment.
  • Scope respected — internal/pagetype's boundary and scaffold/site/AGENTS.md untouched.

Minor nit, non-blocking (not fixed): web/src/test/pagemanage.test.ts uses @ts-expect-error on the node:fs import. If @types/node is ever added, the directive itself becomes an error ("unused") and breaks tsc; @ts-ignore would be inert. Test-file-only, with an explanatory comment.

> *This was generated by AI while landing a PR.* ## Landing audit — PASS, merged **Verification signal relied on:** Forgejo Actions `ci / build-and-test (pull_request)` — **success in 6m2s**. Not re-run locally; the repo's own CI already exercised this diff. **Checked:** - Title is Conventional Commits (`refactor:`). - AFK contract: head `afk/115` carries a working `Closes #115`. - Merged clean into `main` (no conflict, no resolution commit). - Diff reviewed against issue #115's acceptance criteria — all six met. **Review notes:** - `internal/pagepath` is genuinely stdlib-only (`path`, `strings`), satisfying the leaf-package constraint. - The `pageops.effectiveTypeOf` rewrite is behavior-preserving: the old code returned `s` only when `castErr == nil && s != ""`, else fell through to `impliedType`; the new code assigns `explicit = s` on a clean cast and lets `pagepath.EffectiveType` apply the same `!= ""` test. Identical on every path, including the empty-string-type case. - Both Go duplicates deleted; `path`/`strings` remain used in `pagetree/build.go` and `pageops/paths.go`, so no dangling imports. - The shared fixture is real coupling: the Go test and the web test read the same `internal/pagepath/testdata/effective_type_cases.json`. - The "mirrors … byte for byte" comments are gone; the governs-the-children insight survives as a call-site comment. - Scope respected — `internal/pagetype`'s boundary and `scaffold/site/AGENTS.md` untouched. **Minor nit, non-blocking (not fixed):** `web/src/test/pagemanage.test.ts` uses `@ts-expect-error` on the `node:fs` import. If `@types/node` is ever added, the directive itself becomes an error ("unused") and breaks `tsc`; `@ts-ignore` would be inert. Test-file-only, with an explanatory comment.
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!122
No description provided.