diff --git a/HARNESS.md b/HARNESS.md index d410967..8874f3f 100644 --- a/HARNESS.md +++ b/HARNESS.md @@ -54,8 +54,8 @@ changing, because in practice those two drift together — and it fails on a `` repeats itself — "A Khosra Demo · A Khosra Demo" | Spotted 2026-08-01 by looking at the served page, not by any test: `base.html` joins page title and site title unconditionally, and at the root those are the same string. Cosmetic, and the fix is one `if` in a template — theme layer, not engine | The first time the reference theme is worked on (Phase G4 touches it), or sooner if a feed or OpenGraph title inherits the same doubling | | The theme and the index are two separate `atomic.Pointer` stores, so a request landing between them sees a new theme with the previous index | Accepted 2026-08-01 with ADR-0056: both halves are internally coherent and the gap is microseconds, so no page is ever internally inconsistent — it is simply not a snapshot of the disk. Closing it means one pointer holding both, which changes `web.Handler`'s signature and 20 test construction sites | Anything that makes the gap observable — a request rate high enough to land in it, or a feature where content and theme must agree exactly (an export, where every page is generated in one pass) | -| `internal/web/example_test.go` rebuilds the extender list by hand, so it can drift from `cmd/khosra/wire.go` | A package cannot import a `main`, and `extensions.md` puts the list in `cmd` on purpose — nothing below it may know which features exist. Bounded today: the dialect's own test lives in `cmd/khosra/wire_test.go`, beside the real list, and the demo test fails loudly when the copy lags | **Due.** It has now drifted three times in one session — the dialect, `notation`, and `Compose` — each caught by a demo case rather than by the copy itself. The next change to the wiring should move it somewhere a test can import, which needs the list to leave `cmd` without a package below it knowing which features exist | | Under the default include model, a fragment's footnotes render where the include sits, so a long one puts an `
` and a numbered list mid-article | Spotted 2026-08-01 by looking at the served page, not by any test. It is ADR-0038's documented consequence, and the ids are correctly namespaced (ADR-0058); only the placement reads badly. **An author who minds now says `include: merge`** (ADR-0066), which makes the page one document and puts every note at its end, so this is a default rather than a limit | The default itself proving wrong — a site where every composed page sets the flag, at which point the flag is the wrong way round | | The picture memo is never evicted — one entry per picture on the site, for the life of the process | Correct for one author's site, and the alternative is an eviction policy nothing needs. It is keyed on size and modification time, so it cannot go stale, only grow | A site root large enough that memory matters, or a long-running process where pictures churn | diff --git a/docs/surface.md b/docs/surface.md index 59a7b72..e6bb2bb 100644 --- a/docs/surface.md +++ b/docs/surface.md @@ -6,21 +6,21 @@ Every top-level declaration in the engine, with its line. Read this before openi file: it answers "where does X live" and "what is in this package" without the bodies. What each file is *for* lives in `state.md`; why it is that way lives in `decisions.md`. -## cmd/khosra — 306 lines + 91 test +## cmd/khosra — 309 lines + 288 test -check.go 45 · main.go 192 · new.go 42 · wire.go 27 +check.go 45 · main.go 181 · new.go 42 · wire.go 41 - check.go:16 func runCheck(args []string) - main.go:24 func main() - main.go:45 func runServe() -- main.go:107 func pollInterval(dev bool, chosen time.Duration) time.Duration -- main.go:120 func watching(fsys fs.FS, every time.Duration, renderer *render.Renderer, rebuild func() int) -- main.go:137 func rebuilder(fsys fs.FS, cache string, reveal bool, live *atomic.Pointer[content.Site]) func() int -- main.go:163 func given(name string) bool -- main.go:175 func defaultCache() string -- main.go:185 func fatal(msg string, err error) +- main.go:106 func pollInterval(dev bool, chosen time.Duration) time.Duration +- main.go:122 func watching(fsys fs.FS, every time.Duration, renderer *render.Renderer, rebuild func() int) +- main.go:139 func rebuilder(fsys fs.FS, cache string, reveal bool, live *atomic.Pointer[content.Site]) func() int +- main.go:164 func defaultCache() string +- main.go:174 func fatal(msg string, err error) - new.go:12 func runNew(args []string) -- wire.go:17 func extenders(partial render.Partial) []goldmark.Extender +- wire.go:18 func theme(siteFS fs.FS, settings content.Settings) (*render.Renderer, error) +- wire.go:31 func extenders(partial render.Partial) []goldmark.Extender ## internal/content — 1043 lines + 558 test @@ -318,7 +318,7 @@ chrome.go 115 · render.go 456 · view.go 185 - view.go:160 type Picture struct - view.go:177 type Origin struct -## internal/web — 734 lines + 1633 test +## internal/web — 734 lines + 1423 test asset.go 58 · discover.go 71 · extras.go 93 · feed.go 125 · resolve.go 170 · web.go 217