Skip to content

feat(templates,schemas): render audience, derive the journal word count from Zola - #178

Merged
forkwright merged 1 commit into
mainfrom
fix/derived-word-count-and-cascaded-extra
Aug 17, 2026
Merged

feat(templates,schemas): render audience, derive the journal word count from Zola#178
forkwright merged 1 commit into
mainfrom
fix/derived-word-count-and-cascaded-extra

Conversation

@forkwright

@forkwright forkwright commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Two fixes that unblock a consumer, plus a design correction that removed a third. The PR pivoted mid-flight; this body describes what it is, not what it was.

1. The journal word count is derived, not declared

schemas/journal-entry.schema.json no longer requires extra.words / extra.words_source. journal-entry.html and journal-section.html render Zola's own page.word_count. An explicit extra.words still wins as an override, and words_source is required only alongside it (the dependentRequired pattern product.schema.json already uses for availability).

Why: the count was a hand-written claim that drifts. Measured against a consumer's 8 essays, three were materially wrong — ~1050 for 1263 words, ~1150 for 944, ~1120 for 1264. Written once, never updated as the essays were edited. A derived count cannot drift.

What page.word_count actually counts — verified against real builds with isolated per-variable fixtures, not assumed:

Included Excluded
headings, emphasis/bold inner text fenced code block content, entirely
inline code span content
link visible text and the link URL
image alt text and the image src path

Worth stating in the docs, because a reader of the rendered number deserves to know what it measures — particularly on essays containing code fences.

Demonstrated: second-entry (no extra.words) renders 93 words; appending a 23-word sentence renders 133 — hand-checked exact delta. first-entry keeps its ~120 words override.

2. audience is rendered, so requiring it is honest

extra.audience now renders in the entry header beside components, and stays required.

Its schema rationale claimed it was "required so rendered deliverables carry audience assumptions explicitly." Nothing rendered it — no template in this repo, no consumer template, no script. Outside the schema definitions it appeared only in test fixtures that existed to satisfy the requirement. A requirement justified by an effect that does not occur is the defect; the choice was to make the claim true rather than drop the field.

3. What was dropped, and why that is the interesting part

This branch first added an extra cascade, so a section could set audience once for all its pages.

Zola has no such mechanism. A bare [cascade] table is a hard unknown field error at 0.22.1 — verified directly, and confirmed absent through 0.23.3 release notes:

unknown field `cascade`, expected one of `title`, `description`, `sort_by`, `weight`,
`draft`, `template`, ... `page_template`, `aliases`, `generate_feeds`, `extra`

So the cascade was validator-only: it satisfied typikon-validate and told the renderer nothing. A page relying on it passed validation and then failed zola build against the render-time assert — reproduced before it was removed. That is a validate-passes/build-fails trap, and shipping one to fix a metadata gap would have been a bad trade.

Rendering audience moots it entirely: a rendered value must reach Zola, so it must live in the page's own frontmatter. ci/check-extra-cascade.py, the section.core.schema.json change, and the validator's cascade functions are all gone, and the two asserts removed while chasing it are restored — correct again, since they now guard a value that is genuinely rendered.

Verification

  • bash ci/run-fixtures.sh (with ~/.npm-global/bin on PATH so pa11y-ci resolves) → exit 0, every stage pass.
  • bin/typikon-check examples/sample-blog and examples/sample-shopexit 0 each.
  • Rendered output confirmed in built HTML: <p class="entry-audience">…</p> and <p class="specs">93 words</p> on the fixture with no override.

…nt from Zola

Two fixes that unblock a consumer, plus a design correction that removed a third.

DERIVED WORD COUNT. schemas/journal-entry.schema.json no longer REQUIRES
extra.words / extra.words_source; journal-entry.html and journal-section.html
render Zola's own page.word_count instead. An explicit extra.words still wins as
an override, and words_source is required only alongside it.

The count was a hand-written claim that drifted: measured against a consumer's 8
essays, three were off by 13-20 percent -- one claiming ~1050 words for 1263, one
~1150 for 944, one ~1120 for 1264. Written once, never updated as the essays were
edited. A derived count cannot drift.

What page.word_count actually counts, verified against real builds with isolated
per-variable fixtures rather than assumed: headings, emphasis inner text, inline
code span content, link visible text AND the link URL, image alt text AND the
image src path. Fenced code block content is excluded entirely. Worth stating,
because a reader of the number deserves to know what it measures.

RENDERED AUDIENCE. extra.audience is now rendered in the entry header beside
components, and stays required. Its schema rationale said it was 'required so
rendered deliverables carry audience assumptions explicitly' -- and nothing
rendered it. No template in this repo, no consumer template, no script; outside
the schemas it appeared only in fixtures that existed to satisfy the requirement.
A requirement justified by an effect that does not happen is the defect; the
operator chose to make the claim true rather than drop the field.

WHAT WAS DROPPED, and why it is worth recording. This branch first added an
extra-cascade so a section could set audience once for its pages. Zola has no such
mechanism -- a bare [cascade] table is a hard 'unknown field' error at 0.22.1, and
never existed through 0.23.3. The cascade was therefore validator-only: it
satisfied typikon-validate and told the renderer nothing, so a page relying on it
passed validation and then failed zola build against the render-time assert.
Rendering audience moots it entirely, since a rendered value must reach Zola and
so must live in the page's own frontmatter. The two asserts removed while chasing
that are restored, and are correct again.
@forkwright
forkwright force-pushed the fix/derived-word-count-and-cascaded-extra branch from cfb97ef to f194f0f Compare August 17, 2026 20:48
@forkwright forkwright changed the title feat(templates,schemas,bin,ci): derive journal word counts from Zola, let extra cascade through _index.md feat(templates,schemas): render audience, derive the journal word count from Zola Aug 17, 2026
@forkwright
forkwright merged commit af66fa0 into main Aug 17, 2026
4 checks passed
@forkwright
forkwright deleted the fix/derived-word-count-and-cascaded-extra branch August 17, 2026 20:52
forkwright pushed a commit that referenced this pull request Aug 18, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.5.0](v0.4.2...v0.5.0)
(2026-08-17)


### Features

* **templates,css,ci:** expose a real consumer design API, split
Leather's skin out of core
([#175](#175))
([b61228c](b61228c))
* **templates,schemas:** render audience, derive the journal word count
from Zola ([#178](#178))
([af66fa0](af66fa0))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: forkwright <cody@forkwright.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant