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:
@@ -81,8 +81,8 @@ type List struct {
|
||||
Page, Pages int
|
||||
// PrevURL and NextURL are empty at the ends. Newer is "prev" because the order is newest first.
|
||||
PrevURL, NextURL string
|
||||
// Groups is set instead of Items when entries are grouped — a tag listing groups by section, so one
|
||||
// busy term stays readable (ADR-0018).
|
||||
// Groups is the same entries partitioned by section, set alongside Items for a tag listing. Both shapes
|
||||
// are offered because which one to show is the theme's decision, not the engine's (ADR-0046).
|
||||
Groups []Group
|
||||
}
|
||||
|
||||
@@ -98,6 +98,9 @@ type Item struct {
|
||||
Key string
|
||||
URL string
|
||||
Date time.Time
|
||||
// Section is the entry's top-level section, so a flat listing can label where an entry came from without
|
||||
// the engine deciding that it must be grouped (ADR-0046).
|
||||
Section string
|
||||
}
|
||||
|
||||
// Alternate is one language a bundle exists in.
|
||||
@@ -373,13 +376,18 @@ func (r *Renderer) Tag(section, slug, lang string, all []content.Bundle, page in
|
||||
}
|
||||
l, window := r.paginate(title, lang, content.TagURL(section, slug, lang, page), all, page,
|
||||
func(p int) string { return content.TagURL(section, slug, lang, p) })
|
||||
// Both shapes, always: the flat list in query order, and the same entries partitioned by section. A
|
||||
// template cannot group for itself, so the engine offers the partition — but it does not decide that a tag
|
||||
// listing must look grouped, which is a readability judgement belonging to whoever writes the markup
|
||||
// (ADR-0046, amending ADR-0032).
|
||||
for _, b := range window {
|
||||
sec := b.Section()
|
||||
if n := len(l.Groups); n > 0 && l.Groups[n-1].Name == sec {
|
||||
l.Groups[n-1].Items = append(l.Groups[n-1].Items, r.item(b, lang))
|
||||
item := r.item(b, lang)
|
||||
l.Items = append(l.Items, item)
|
||||
if n := len(l.Groups); n > 0 && l.Groups[n-1].Name == item.Section {
|
||||
l.Groups[n-1].Items = append(l.Groups[n-1].Items, item)
|
||||
continue
|
||||
}
|
||||
l.Groups = append(l.Groups, Group{Name: sec, Items: []Item{r.item(b, lang)}})
|
||||
l.Groups = append(l.Groups, Group{Name: item.Section, Items: []Item{item}})
|
||||
}
|
||||
return r.execute(r.list, l, "tag "+slug)
|
||||
}
|
||||
@@ -416,7 +424,7 @@ func (r *Renderer) sequence(seq *content.Sequence, lang string) *Sequence {
|
||||
|
||||
// item is one listing entry.
|
||||
func (r *Renderer) item(b content.Bundle, lang string) Item {
|
||||
return Item{Title: b.Title, Key: b.Key, URL: content.URL(b.Route, lang), Date: b.Date}
|
||||
return Item{Title: b.Title, Key: b.Key, URL: content.URL(b.Route, lang), Date: b.Date, Section: b.Section()}
|
||||
}
|
||||
|
||||
// paginate builds the shell of a listing page and returns the slice of entries it shows.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user