audit every feature against the layer test

Deleting the widows feature answered one question; the human asked the general one.
So every feature built so far is now audited, with the verdicts as a table in
ADR-0046, and each remaining queue entry carries a layer note before anyone writes
code for it.

The codebase turned out to be otherwise clean, and I checked rather than remembered:
no Go file writes a tag, a class or a style. ADR-0036's rule that all markup comes
from a fragment had already forced that.

One real finding. A tag listing received only `.Groups`, so the engine had decided
that a tag listing *looks* grouped — ADR-0032's own reasoning was "so a busy term
stays readable", which is a readability judgement. It now receives both shapes: the
partition, because a template cannot group for itself, and the flat list, because
choosing between them is markup. `Item` gained `.Section` so a flat listing can still
say where an entry came from.

Two judgement calls recorded rather than left implicit. The typographer stays: turning
`--` into an en dash is a character transformation no stylesheet can express. Chrome
strings stay: translations are data, and the alternative is every theme hardcoding
Bengali month names. The inlined stylesheet is accepted with its cost written down —
bytes per page, no caching — and a trigger for revisiting it.

Two patterns worth reusing came out of this: offer the shape rather than choosing it,
and a split feature is normal — search will be an engine-built index queried by the
browser, not one or the other.
This commit is contained in:
2026-08-01 02:23:36 +06:00
parent a2ccb45b0d
commit 959865e334
6 changed files with 109 additions and 12 deletions
+35
View File
@@ -161,3 +161,38 @@ func TestWithoutABaseEverythingStaysRelative(t *testing.T) {
t.Error("no declared title means no site_name tag, rather than an empty one")
}
}
func TestATagListingOffersBothShapesAndLetsTheThemeChoose(t *testing.T) {
// The engine may not decide that a tag listing looks grouped (ADR-0046). It supplies the partition, because
// a template cannot group for itself, and the flat list beside it, because choosing is markup.
r, err := New(nil, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
var bundles []content.Bundle
for _, spec := range []struct{ name, section string }{
{"posts/essay", "posts"}, {"comics/strip", "comics"}, {"posts/other", "posts"},
} {
b, err := content.Parse(spec.name+".md", []byte("---\ntitle: "+spec.name+"\n---\n"))
if err != nil {
t.Fatal(err)
}
b.Route = b.Key
bundles = append(bundles, b)
}
out, err := r.Tag("", "monsoon", "en", bundles, 1)
if err != nil {
t.Fatal(err)
}
// The reference theme renders groups, so the sections appear as headings.
got := string(out)
for _, want := range []string{"<h2>posts</h2>", "<h2>comics</h2>"} {
if !strings.Contains(got, want) {
t.Errorf("missing %q:\n%s", want, got)
}
}
// A theme preferring a flat list must have one, with each entry able to say where it came from.
if !strings.Contains(got, "posts/essay") || !strings.Contains(got, "comics/strip") {
t.Errorf("every entry should be present whichever shape is used:\n%s", got)
}
}