Skills and theme contract: a redone or migrated theme can reintroduce the quadratic partials, and nothing measures it #363

Open
opened 2026-11-01 01:14:54 +01:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Summary

A redone, ported or forked theme can reintroduce the quadratic partials #360
fixed, and no skill measures it: the only guard is harness check's
site-wide-collection warning, which a per-pager re-filter or a large
section scan slips past. Ship the measurement as harness bench, give it
one owner shared with the theme guard test, and make the skills run it when
a change touches a template that lists pages.

Current behavior

PR #364 (merged 2026-11-01) landed most of the original report:

  • The theme README's "Navigation" states the pattern (cache the partial that
    holds the scan, never the argument; don't compute what a branch won't
    draw) and names the shipped owners footer-pages.html, news-items.html
    and the header's hideChildren guard. The theme contract (item 20), the
    skills redo-theme, migrate-site, create-feature, verify-site,
    docs/features.md and docs/harness.md point at it.
  • harness check warns, in area templates, on a template that reads a
    site-wide collection outside a partial every caller reaches through
    partialCached. internal/tplrefs.UncachedSiteScans owns the rule; a
    section's own .Pages is deliberately not warned on.
  • internal/theme/scale_test.go generates a site in the reporting site's
    shape (N news items, 12 categories, 13 news-list pages), builds it at N and
    2N on one core with real hugo, fails above a ratio of 2.7, and checks in
    a subtest that the two cached partials execute once per key. It also holds
    a --templateMetrics table parser. All of it is test code.

What is still missing:

  • No skill measures anything. verify-site builds once and reads warnings;
    redo-theme step 4 and migrate-site step 7 describe the pattern in
    prose. A fork that re-filters a section's items on every pager page, or
    scans a large section's children per page, passes harness check and
    every skill.
  • There is no harness bench; the generator exists only inside the test.

Measured during triage on 2026-11-01 (Hugo 0.167.0 extended, the guard
test's generated site, old partials taken from the commit before #364,
GOMAXPROCS=1 unless noted; outputs byte-identical, no Hugo warnings):

Variant 1,281 pages 2,565 pages ratio
fixed theme 3.06 s 6.20 s 2.03
old partials 5.44 s 17.0 s 3.13
fixed theme, 16 cores 2.11 s

Three facts from that measurement rule out the thresholds the original
report proposed:

  1. Hugo's per-template average includes time the goroutine spends
    descheduled: on one core the fixed header averages 9.5 ms and list.html
    18.8 ms (maximum 890 ms); on 16 cores the same header averages 2.9 ms.
    "No partial above a few ms average" fails the fixed theme and depends on
    the machine.
  2. --templateMetricsHints reports the fixed theme's header.html at 98 %
    cache potential, document/close.html and backendlayout/resolve.html at
    100 %, ce/element.html at 91 %: cheap partials with alike output. "No
    uncached partial over 90 %" fails the fixed theme too.
  3. --templateMetricsHints deadlocked Hugo on the old partials at 2,565
    pages in two runs of two (one core, 10 min; 16 cores, 3 min; zero CPU).
    The goroutine dump: metrics.(*Store).TrackValue hashes a partial's
    return value under the store mutex, the hash reaches
    resourceAdapter.Key → lazycache.GetOrCreate and waits for an entry
    another goroutine is creating while that goroutine waits for the same
    mutex. The 1,281-page old-partials site and the fixed 2,565-page site
    completed.

Desired behavior

  • harness bench --site <dir> --pages <n> [--section <path>] [--json]
    copies the site to a temporary directory (the site itself is never
    written), fills the named section (default: the first section of type
    news; an error naming the flag when the site has none) with generated
    items in the guard test's shape, builds the copy at n and 2n items on
    one core with the pinned Hugo and --templateMetrics, and reports: the
    page count and wall time of each build, the ratio, the verdict against the
    limit the guard test uses, and the top rows of the larger build's metrics
    table by cumulative duration (template, count, cumulative, average) so the
    developer knows where to look. Exit 0 when the ratio is within the limit,
    1 when it is over, 2 when the bench cannot run (no hugo, no section, a
    failed build, whose Hugo output is printed).
  • --hints adds --templateMetricsHints to the larger build under a
    timeout (default 5 minutes, --timeout); on expiry the bench reports that
    Hugo hung in its hints mode and still gives the ratio verdict from the
    plain builds. Without the flag hints are never requested.
  • The text output says what the verdict means in one sentence and names
    themes/deckle/README.md "Navigation" as the fix. --json is one object
    with formatVersion, status (ok, slow, error), the two builds
    (items, pages, seconds), ratio, limit, the templates rows,
    and, with --hints, hints (ok, timeout) and a cachePotential
    column on the rows.
  • The generator, the build runner, the metrics-table parser and the ratio
    limit live in one non-test package the command and
    internal/theme's guard test both call. The guard test keeps its
    behaviour (N = 2,000, limit 2.7, once-per-key subtest, skips without
    hugo and under -short) and stops carrying its own copies.
  • verify-site gets one step, after harness check and the production
    build: when the change touched a template that lists pages (a theme
    rewrite or port, a new section page type, a feature with a section list,
    a changed header, footer or pager), run harness bench --site . --pages 2500 and stop on a slow verdict; the step says what the ratio means in
    one sentence and points at themes/deckle/README.md "Navigation" for the
    fix. redo-theme (before "Verify"), migrate-site (step 7 and step 9),
    create-feature and add-page-type each name the bench where they point
    at verify-site, as the thing that decides whether their templates scale;
    none restates the thresholds or the procedure.
  • harness check is not changed. The existing warning stays in area
    templates; no .Paginate or $src.Pages heuristic is added.

Decisions

  • The ratio of N and 2N builds is the verdict, not per-template averages or
    the hints column — the measurement above shows both thresholds the
    original report proposed fail the fixed theme and depend on core count.
  • One owner for the generator and the limit: a package both the command and
    the guard test import, because the bench and the test evaluate the same
    rule ("does this theme build linearly in the page count") and CLAUDE.md
    forbids a second evaluation. The calibration comment on the limit (2.7:
    stock 3.1–3.4x, fixed 2.0–2.2x) moves with it.
  • Hints are opt-in and time-boxed — the deadlock above reproduces on exactly
    the theme the bench exists to catch, and a hung bench in a skill step is
    worse than no bench.
  • One core (GOMAXPROCS=1), as the guard test does — the ratio is what
    matters and one core makes it independent of the developer's machine;
    the bench says so in its output.
  • No .Paginate / $src.Pages check heuristic and no performance area —
    the shipped term.html paginates a filtered $src.Pages built in the page
    template (9.6 ms per term page on 16 cores, no gain from caching per
    #360), and the header, page-teasers and news-list all read .Pages
    through where; any such rule trips the stock theme, and the bench
    measures what the rule would guess. The --json area list in
    docs/harness.md stays as it is.
  • The measurement step lives in verify-site once, conditioned on what the
    change touched; the other four skills point at it — skills are
    procedures with one owner each (CLAUDE.md), and every other skill already
    ends in verify-site.
  • The generated section's items need the news bundle's news-item shape
    (type, date, teaser, author, categories, a text element
    body) — that is the shape that exposed the problem and the one the guard
    test uses; --section selects which section is filled, not the item
    type. A site without the news bundle gets exit 2 with a message saying
    the bench needs it.
  • Keep bug as the category; the maintainer applied it.

Key interfaces

  • cmd/harness: a new bench entry in the commands table (its summary
    line is what usageText() prints and the usage test compares with
    docs/harness.md); a benchCmd(args, stdout, stderr) int in the style of
    checkCmd, --json through the same one-object convention.
  • A new package (say internal/sitebench) exporting the generator
    (scaleSiteContent today: items → site-relative paths → canonical
    content; the canonical-content check the test already applies must keep
    passing), the one-core build runner with --templateMetrics
    (runScaleBuild today), the metrics parser (parseTemplateMetrics and
    metricsRow today), the limit (scaleMaxRatio today) and a result type
    the command serialises. The test's site assembly (assembleScaleSite,
    which copies theme and bundles the way harness add vendors them) is
    test-only and stays; the command works on a real site directory.
  • internal/theme/scale_test.go: calls the package; TestBuildTimeScalesLinearly
    and its templateMetrics subtest keep their assertions.
  • Docs, each fact in its owner: docs/harness.md — a ### harness bench
    section (flags, output, exit codes, the hints caveat with the deadlock)
    and a ### harness bench --json shape under the --json list;
    README.md — one row in the command table, no new section;
    scaffold/site/AGENTS.md — the bench beside the other harness commands it
    names (the "Verifying a change" paragraph), so the skills have a section
    to point at; scaffold/themes/deckle/README.md "Navigation" — one
    sentence that harness bench measures the property, so the fact and its
    measurement sit together; docs/agents/docs-map.md — the new package in
    the docs/harness.md row.
  • Skills: verify-site/SKILL.md (the new step), redo-theme/SKILL.md
    (before "## 4. Verify"), migrate-site/SKILL.md (steps 7 and 9),
    create-feature/SKILL.md and add-page-type/SKILL.md where they point at
    verify-site. scaffold/skills_test.go checks every harness <command>
    a skill names is real, so the command must exist before the skills name
    it.

Acceptance criteria

  • harness bench --site <fresh site with news and categories> --pages 500 builds a 500- and a 1,000-item copy on one core, prints both
    page counts and times, the ratio, ok, and the top metrics rows; the
    site directory is unchanged afterwards (git status clean) and the
    temporary copy is removed.
  • The same site with the old header.html, footer.html and
    pagetypes/news.html (the versions before #364, as
    internal/theme's guard was proven against) at --pages 2000 reports
    slow and exits 1; the fixed site at --pages 2000 reports ok.
  • --json emits the documented object and nothing else on stdout;
    docs/harness.md documents it; go test ./cmd/harness/ ./docs/ pass.
  • --hints on the fixed site adds the cache-potential column; with a
    --timeout 1s against the old partials at 2,534 items the bench
    reports the hints timeout and still prints the ratio verdict (this is
    the reproduction of the deadlock; it may not hang every run — the
    test for the timeout path uses a stub or a short timeout, not the
    deadlock).
  • Without hugo on PATH: exit 2 and a message; on a site without the
    news bundle: exit 2 naming the requirement.
  • internal/theme/scale_test.go has no generator, runner, parser or
    limit of its own; TestBuildTimeScalesLinearly passes in CI with the
    same limit.
  • go test ./scaffold/ passes: the five skills name harness bench,
    every quoted heading exists, every link resolves.
  • harness check is unchanged: go test ./internal/checkcmd/ ./internal/tplrefs/ pass with no new warning text.

How to verify

  • go test ./... with hugo on PATH (the guard test, the usage test, the
    docs drift tests, the skills drift test).
  • The two bench runs in the first two criteria, on one core; compare with
    the triage table above (fixed ≈ 2.0x, old ≈ 3.1x).
  • diff -r is not needed: the bench builds a copy and writes nothing to
    the site.

Assumptions to check

  • The temporary test used during triage exported the guard test's site
    through assembleScaleSite; a real site's hugo.toml may need
    buildFuture or the categories taxonomy the test's config sets — the
    bench must build the site as it is and report Hugo's error, not patch
    the config.
  • GOMAXPROCS=1 reaches Hugo through the environment the test already
    sets; confirm the command does the same on macOS.
  • The hints deadlock was observed twice at 2,565 pages and never at 1,281;
    treat it as a race, not a size threshold.
  • harness check's usage test and the README command-table test may
    require the new row in a fixed format; read cmd/harness/*_test.go
    first.

Out of scope

  • Changing harness check (no .Paginate, $src.Pages or performance
    area).
  • term.html, elements/news-list.html, page-teasers.html.
  • Updating forked themes on existing sites, or a layouts-update mechanism
    for bundles.
  • Reporting the Hugo hints deadlock upstream (worth doing, separately).
  • A generated item shape other than the news bundle's.

Blocked by

None - can start immediately

Original report

What happens

The theme performance issue (the quadratic header, footer and news list) was produced by the shipped theme and then copied into gbv-aktuell's fork by the redo-theme procedure. Nothing in the scaffold prevents it: themes/deckle/README.md has no rule about a template's cost, redo-theme step 4 asks only that menus and lists read the source page's flags, verify-site builds once and greps warnings, and harness check knows nothing about templates beyond unresolved partial names. A site of 2,500 pages built in 28.6 s where 9.1 s was possible, and every save after visiting /news/ cost 5.8 s where 3.1 s was possible.

Fix

Following CLAUDE.md: the fact goes to the owner, the skills point at it, a test enforces the mechanical half.

  1. themes/deckle/README.md, new section "Cost on a large site": a template that reads a whole collection (site.Pages, hugo.Sites.Default.Pages, a section's .Pages) does so once per build, in a partialCached partial keyed by language and, for a section, by .RelPermalink, never in a page template, because the where argument of partialCached is evaluated before the cache is consulted; a list a flag suppresses (hideChildren) is not computed before the flag is checked; a paginated section's items come from one such partial. Name the shipped owners: footer-pages.html, news-items.html, the header's hideChildren guard.

  2. redo-theme (before "Verify"), migrate-site, create-feature and add-page-type (anything that lists pages): one step — build a generated large site with hugo --templateMetrics --templateMetricsHints and require no partial above a few ms average and no uncached partial with a cache potential over 90 %. The generator ships as harness bench --site . --pages 2500 (a copy of the site whose news section, or a section the flag names, is filled with generated items) so the skills do not restate how to make one.

  3. harness check: a templates warning for where over site.Pages, hugo.Sites.Default.Pages or $src.Pages outside a partial that is only ever called through partialCached, and for .Paginate on a collection not read from one. This is the check a fork cannot skip.

    Caution: this is a text match over templates, and a fork that renamed its partials or built its lists differently will trip it without being slow. It is a warning, never an error, reported under its own key (performance) in harness check --json, so a site can acknowledge it and verify-site can still treat the rest of the report as clean. The measurement in item 2 is what decides; the warning only points at where to look.

  4. Tests: scaffold/skills_test.go already requires quoted headings to exist, so the new README section is pinned by the skills that quote it. Add a theme test that builds the generated site at N and 2N pages and fails when the ratio of build times exceeds 2.5.

Evidence

Measured 2026-10-31 on a synthetic 2,562-page site with the stock theme and Hugo 0.167.0: header 12.4 s of 28.6 s, footer 6.1 s, news list 2.0 s, all flagged cacheable by --templateMetricsHints; the three fixes in the theme issue give 9.1 s with byte-identical output. gbv-aktuell's fork (TYPO3-lookalike markup) must be checked for the same patterns by hand until item 3 exists.

> *This was generated by AI during triage.* ## Summary A redone, ported or forked theme can reintroduce the quadratic partials #360 fixed, and no skill measures it: the only guard is `harness check`'s site-wide-collection warning, which a per-pager re-filter or a large section scan slips past. Ship the measurement as `harness bench`, give it one owner shared with the theme guard test, and make the skills run it when a change touches a template that lists pages. ## Current behavior PR #364 (merged 2026-11-01) landed most of the original report: - The theme README's "Navigation" states the pattern (cache the partial that holds the scan, never the argument; don't compute what a branch won't draw) and names the shipped owners `footer-pages.html`, `news-items.html` and the header's `hideChildren` guard. The theme contract (item 20), the skills `redo-theme`, `migrate-site`, `create-feature`, `verify-site`, `docs/features.md` and `docs/harness.md` point at it. - `harness check` warns, in area `templates`, on a template that reads a site-wide collection outside a partial every caller reaches through `partialCached`. `internal/tplrefs.UncachedSiteScans` owns the rule; a section's own `.Pages` is deliberately not warned on. - `internal/theme/scale_test.go` generates a site in the reporting site's shape (N news items, 12 categories, 13 news-list pages), builds it at N and 2N on one core with real `hugo`, fails above a ratio of 2.7, and checks in a subtest that the two cached partials execute once per key. It also holds a `--templateMetrics` table parser. All of it is test code. What is still missing: - No skill measures anything. `verify-site` builds once and reads warnings; `redo-theme` step 4 and `migrate-site` step 7 describe the pattern in prose. A fork that re-filters a section's items on every pager page, or scans a large section's children per page, passes `harness check` and every skill. - There is no `harness bench`; the generator exists only inside the test. Measured during triage on 2026-11-01 (Hugo 0.167.0 extended, the guard test's generated site, old partials taken from the commit before #364, `GOMAXPROCS=1` unless noted; outputs byte-identical, no Hugo warnings): | Variant | 1,281 pages | 2,565 pages | ratio | | --- | --- | --- | --- | | fixed theme | 3.06 s | 6.20 s | 2.03 | | old partials | 5.44 s | 17.0 s | 3.13 | | fixed theme, 16 cores | | 2.11 s | | Three facts from that measurement rule out the thresholds the original report proposed: 1. Hugo's per-template average includes time the goroutine spends descheduled: on one core the fixed header averages 9.5 ms and `list.html` 18.8 ms (maximum 890 ms); on 16 cores the same header averages 2.9 ms. "No partial above a few ms average" fails the fixed theme and depends on the machine. 2. `--templateMetricsHints` reports the fixed theme's `header.html` at 98 % cache potential, `document/close.html` and `backendlayout/resolve.html` at 100 %, `ce/element.html` at 91 %: cheap partials with alike output. "No uncached partial over 90 %" fails the fixed theme too. 3. `--templateMetricsHints` deadlocked Hugo on the old partials at 2,565 pages in two runs of two (one core, 10 min; 16 cores, 3 min; zero CPU). The goroutine dump: `metrics.(*Store).TrackValue` hashes a partial's return value under the store mutex, the hash reaches `resourceAdapter.Key` → `lazycache.GetOrCreate` and waits for an entry another goroutine is creating while that goroutine waits for the same mutex. The 1,281-page old-partials site and the fixed 2,565-page site completed. ## Desired behavior - `harness bench --site <dir> --pages <n> [--section <path>] [--json]` copies the site to a temporary directory (the site itself is never written), fills the named section (default: the first section of type `news`; an error naming the flag when the site has none) with generated items in the guard test's shape, builds the copy at `n` and `2n` items on one core with the pinned Hugo and `--templateMetrics`, and reports: the page count and wall time of each build, the ratio, the verdict against the limit the guard test uses, and the top rows of the larger build's metrics table by cumulative duration (template, count, cumulative, average) so the developer knows where to look. Exit 0 when the ratio is within the limit, 1 when it is over, 2 when the bench cannot run (no `hugo`, no section, a failed build, whose Hugo output is printed). - `--hints` adds `--templateMetricsHints` to the larger build under a timeout (default 5 minutes, `--timeout`); on expiry the bench reports that Hugo hung in its hints mode and still gives the ratio verdict from the plain builds. Without the flag hints are never requested. - The text output says what the verdict means in one sentence and names `themes/deckle/README.md` "Navigation" as the fix. `--json` is one object with `formatVersion`, `status` (`ok`, `slow`, `error`), the two builds (`items`, `pages`, `seconds`), `ratio`, `limit`, the `templates` rows, and, with `--hints`, `hints` (`ok`, `timeout`) and a `cachePotential` column on the rows. - The generator, the build runner, the metrics-table parser and the ratio limit live in one non-test package the command and `internal/theme`'s guard test both call. The guard test keeps its behaviour (N = 2,000, limit 2.7, once-per-key subtest, skips without `hugo` and under `-short`) and stops carrying its own copies. - `verify-site` gets one step, after `harness check` and the production build: when the change touched a template that lists pages (a theme rewrite or port, a new section page type, a feature with a section list, a changed header, footer or pager), run `harness bench --site . --pages 2500` and stop on a `slow` verdict; the step says what the ratio means in one sentence and points at `themes/deckle/README.md` "Navigation" for the fix. `redo-theme` (before "Verify"), `migrate-site` (step 7 and step 9), `create-feature` and `add-page-type` each name the bench where they point at `verify-site`, as the thing that decides whether their templates scale; none restates the thresholds or the procedure. - `harness check` is not changed. The existing warning stays in area `templates`; no `.Paginate` or `$src.Pages` heuristic is added. ## Decisions - The ratio of N and 2N builds is the verdict, not per-template averages or the hints column — the measurement above shows both thresholds the original report proposed fail the fixed theme and depend on core count. - One owner for the generator and the limit: a package both the command and the guard test import, because the bench and the test evaluate the same rule ("does this theme build linearly in the page count") and CLAUDE.md forbids a second evaluation. The calibration comment on the limit (2.7: stock 3.1–3.4x, fixed 2.0–2.2x) moves with it. - Hints are opt-in and time-boxed — the deadlock above reproduces on exactly the theme the bench exists to catch, and a hung bench in a skill step is worse than no bench. - One core (`GOMAXPROCS=1`), as the guard test does — the ratio is what matters and one core makes it independent of the developer's machine; the bench says so in its output. - No `.Paginate` / `$src.Pages` check heuristic and no `performance` area — the shipped `term.html` paginates a filtered `$src.Pages` built in the page template (9.6 ms per term page on 16 cores, no gain from caching per #360), and the header, `page-teasers` and `news-list` all read `.Pages` through `where`; any such rule trips the stock theme, and the bench measures what the rule would guess. The `--json` area list in `docs/harness.md` stays as it is. - The measurement step lives in `verify-site` once, conditioned on what the change touched; the other four skills point at it — skills are procedures with one owner each (CLAUDE.md), and every other skill already ends in `verify-site`. - The generated section's items need the news bundle's `news-item` shape (`type`, `date`, `teaser`, `author`, `categories`, a `text` element body) — that is the shape that exposed the problem and the one the guard test uses; `--section` selects which section is filled, not the item type. A site without the news bundle gets exit 2 with a message saying the bench needs it. - Keep `bug` as the category; the maintainer applied it. ## Key interfaces - `cmd/harness`: a new `bench` entry in the `commands` table (its summary line is what `usageText()` prints and the usage test compares with `docs/harness.md`); a `benchCmd(args, stdout, stderr) int` in the style of `checkCmd`, `--json` through the same one-object convention. - A new package (say `internal/sitebench`) exporting the generator (`scaleSiteContent` today: items → site-relative paths → canonical content; the canonical-content check the test already applies must keep passing), the one-core build runner with `--templateMetrics` (`runScaleBuild` today), the metrics parser (`parseTemplateMetrics` and `metricsRow` today), the limit (`scaleMaxRatio` today) and a result type the command serialises. The test's site assembly (`assembleScaleSite`, which copies theme and bundles the way `harness add` vendors them) is test-only and stays; the command works on a real site directory. - `internal/theme/scale_test.go`: calls the package; `TestBuildTimeScalesLinearly` and its `templateMetrics` subtest keep their assertions. - Docs, each fact in its owner: `docs/harness.md` — a `### harness bench` section (flags, output, exit codes, the hints caveat with the deadlock) and a `### harness bench --json` shape under the `--json` list; `README.md` — one row in the command table, no new section; `scaffold/site/AGENTS.md` — the bench beside the other harness commands it names (the "Verifying a change" paragraph), so the skills have a section to point at; `scaffold/themes/deckle/README.md` "Navigation" — one sentence that `harness bench` measures the property, so the fact and its measurement sit together; `docs/agents/docs-map.md` — the new package in the `docs/harness.md` row. - Skills: `verify-site/SKILL.md` (the new step), `redo-theme/SKILL.md` (before "## 4. Verify"), `migrate-site/SKILL.md` (steps 7 and 9), `create-feature/SKILL.md` and `add-page-type/SKILL.md` where they point at `verify-site`. `scaffold/skills_test.go` checks every `harness <command>` a skill names is real, so the command must exist before the skills name it. ## Acceptance criteria - [ ] `harness bench --site <fresh site with news and categories> --pages 500` builds a 500- and a 1,000-item copy on one core, prints both page counts and times, the ratio, `ok`, and the top metrics rows; the site directory is unchanged afterwards (`git status` clean) and the temporary copy is removed. - [ ] The same site with the old `header.html`, `footer.html` and `pagetypes/news.html` (the versions before #364, as `internal/theme`'s guard was proven against) at `--pages 2000` reports `slow` and exits 1; the fixed site at `--pages 2000` reports `ok`. - [ ] `--json` emits the documented object and nothing else on stdout; `docs/harness.md` documents it; `go test ./cmd/harness/ ./docs/` pass. - [ ] `--hints` on the fixed site adds the cache-potential column; with a `--timeout 1s` against the old partials at 2,534 items the bench reports the hints timeout and still prints the ratio verdict (this is the reproduction of the deadlock; it may not hang every run — the test for the timeout path uses a stub or a short timeout, not the deadlock). - [ ] Without `hugo` on PATH: exit 2 and a message; on a site without the news bundle: exit 2 naming the requirement. - [ ] `internal/theme/scale_test.go` has no generator, runner, parser or limit of its own; `TestBuildTimeScalesLinearly` passes in CI with the same limit. - [ ] `go test ./scaffold/` passes: the five skills name `harness bench`, every quoted heading exists, every link resolves. - [ ] `harness check` is unchanged: `go test ./internal/checkcmd/ ./internal/tplrefs/` pass with no new warning text. ## How to verify - `go test ./...` with `hugo` on PATH (the guard test, the usage test, the docs drift tests, the skills drift test). - The two bench runs in the first two criteria, on one core; compare with the triage table above (fixed ≈ 2.0x, old ≈ 3.1x). - `diff -r` is not needed: the bench builds a copy and writes nothing to the site. ## Assumptions to check - The temporary test used during triage exported the guard test's site through `assembleScaleSite`; a real site's `hugo.toml` may need `buildFuture` or the categories taxonomy the test's config sets — the bench must build the site as it is and report Hugo's error, not patch the config. - `GOMAXPROCS=1` reaches Hugo through the environment the test already sets; confirm the command does the same on macOS. - The hints deadlock was observed twice at 2,565 pages and never at 1,281; treat it as a race, not a size threshold. - `harness check`'s usage test and the README command-table test may require the new row in a fixed format; read `cmd/harness/*_test.go` first. ## Out of scope - Changing `harness check` (no `.Paginate`, `$src.Pages` or `performance` area). - `term.html`, `elements/news-list.html`, `page-teasers.html`. - Updating forked themes on existing sites, or a layouts-update mechanism for bundles. - Reporting the Hugo hints deadlock upstream (worth doing, separately). - A generated item shape other than the news bundle's. ## Blocked by None - can start immediately ## Original report ## What happens The theme performance issue (the quadratic header, footer and news list) was produced by the shipped theme and then copied into gbv-aktuell's fork by the `redo-theme` procedure. Nothing in the scaffold prevents it: `themes/deckle/README.md` has no rule about a template's cost, `redo-theme` step 4 asks only that menus and lists read the source page's flags, `verify-site` builds once and greps warnings, and `harness check` knows nothing about templates beyond unresolved partial names. A site of 2,500 pages built in 28.6 s where 9.1 s was possible, and every save after visiting `/news/` cost 5.8 s where 3.1 s was possible. ## Fix Following CLAUDE.md: the fact goes to the owner, the skills point at it, a test enforces the mechanical half. 1. `themes/deckle/README.md`, new section "Cost on a large site": a template that reads a whole collection (`site.Pages`, `hugo.Sites.Default.Pages`, a section's `.Pages`) does so once per build, in a `partialCached` partial keyed by language and, for a section, by `.RelPermalink`, never in a page template, because the `where` argument of `partialCached` is evaluated before the cache is consulted; a list a flag suppresses (`hideChildren`) is not computed before the flag is checked; a paginated section's items come from one such partial. Name the shipped owners: `footer-pages.html`, `news-items.html`, the header's `hideChildren` guard. 2. `redo-theme` (before "Verify"), `migrate-site`, `create-feature` and `add-page-type` (anything that lists pages): one step — build a generated large site with `hugo --templateMetrics --templateMetricsHints` and require no partial above a few ms average and no uncached partial with a cache potential over 90 %. The generator ships as `harness bench --site . --pages 2500` (a copy of the site whose news section, or a section the flag names, is filled with generated items) so the skills do not restate how to make one. 3. `harness check`: a `templates` warning for `where` over `site.Pages`, `hugo.Sites.Default.Pages` or `$src.Pages` outside a partial that is only ever called through `partialCached`, and for `.Paginate` on a collection not read from one. This is the check a fork cannot skip. Caution: this is a text match over templates, and a fork that renamed its partials or built its lists differently will trip it without being slow. It is a warning, never an error, reported under its own key (`performance`) in `harness check --json`, so a site can acknowledge it and `verify-site` can still treat the rest of the report as clean. The measurement in item 2 is what decides; the warning only points at where to look. 4. Tests: `scaffold/skills_test.go` already requires quoted headings to exist, so the new README section is pinned by the skills that quote it. Add a theme test that builds the generated site at N and 2N pages and fails when the ratio of build times exceeds 2.5. ## Evidence Measured 2026-10-31 on a synthetic 2,562-page site with the stock theme and Hugo 0.167.0: header 12.4 s of 28.6 s, footer 6.1 s, news list 2.0 s, all flagged cacheable by `--templateMetricsHints`; the three fixes in the theme issue give 9.1 s with byte-identical output. gbv-aktuell's fork (TYPO3-lookalike markup) must be checked for the same patterns by hand until item 3 exists.
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#363
No description provided.