From 69f43e5791b141ac9feae7dd77c604d20958a5d3 Mon Sep 17 00:00:00 2001 From: bdeshi Date: Sun, 2 Aug 2026 19:00:51 +0600 Subject: [PATCH] enable task lists, superseding the decision that excluded them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-0058 kept task lists out as "a note-taking affordance, not a publishing one". That reasoning measured the wrong axis: a checklist inside a published technical piece — setup steps, a runbook, a what-I-tried list — is publishing, and nothing else in the dialect expresses "this item is done" without the author hand-writing an entity. ADR-0078 supersedes that half and records why. The half of ADR-0058 that mattered is untouched: extension.GFM stays refused, because the bundle drags linkify in with the tables it is wanted for, and linkify rewrites an author's plain text into markup — the line ADR-0034 draws. One named extension is not a bundle, and wire.go now says so where the temptation to reach for GFM will next appear. ADR-0058's Status line names its successor, so a reader arriving there learns the task-list sentence no longer holds. Same in-place Status annotation the mutability rule allows, Decision text untouched. Checkboxes render disabled: static markup, nothing clickable, nothing stored. A reader with scripting off sees the same page, which is the property the whole theme is built on. goldmark adds no class to the list, so theme.css finds it with :has rather than the engine inventing markup to be styled by. Demo carries the case ADR-0051 requires — a colophon checklist of what this build does and does not do, including the unticked "ship a single byte of JavaScript", which is true of that page and asserted by the test's absent list. No counter moves: state.md's counters are explicit that an upstream extension enabled in the list is dialect, not a feature of this engine — only a package under internal/ext/ counts. 8 files, +55/-10. 1 line of engine code. Co-Authored-By: Claude Opus 5 --- cmd/khosra/example_test.go | 3 +++ cmd/khosra/wire.go | 3 +++ docs/content-model.md | 10 ++++---- docs/decisions.md | 24 ++++++++++++++++++- docs/state.md | 6 ++--- docs/surface.md | 4 ++-- .../demo-site/content/pages/colophon.en.md | 10 ++++++++ internal/render/templates/theme.css | 5 ++++ 8 files changed, 55 insertions(+), 10 deletions(-) diff --git a/cmd/khosra/example_test.go b/cmd/khosra/example_test.go index bc1433f..419d16e 100644 --- a/cmd/khosra/example_test.go +++ b/cmd/khosra/example_test.go @@ -132,6 +132,9 @@ var exampleFeatures = []featureCase{ absent: []string{"`, "Serve your own"}, absent: []string{"", "What is deliberately not shown"}}, {what: "an admonition renders inside a comic chapter too", path: "/comics/the-long-monsoon/the-flood/", code: 200, diff --git a/cmd/khosra/wire.go b/cmd/khosra/wire.go index 72d99ea..fee1024 100644 --- a/cmd/khosra/wire.go +++ b/cmd/khosra/wire.go @@ -35,6 +35,9 @@ func extenders(partial render.Partial) []goldmark.Extender { extension.Table, extension.NewFootnote(extension.WithFootnoteIDPrefixFunction(shortcodes.FootnotePrefix)), extension.DefinitionList, + // TaskList alone, never extension.GFM: the bundle would drag linkify in with it, and linkify + // rewrites an author's plain text into markup, which is the line ADR-0034 draws (ADR-0078). + extension.TaskList, notation.New(), shortcodes.New(partial), } diff --git a/docs/content-model.md b/docs/content-model.md index adccb84..4cf39c4 100644 --- a/docs/content-model.md +++ b/docs/content-model.md @@ -298,6 +298,7 @@ them is a render transform, and the list lives in `cmd/khosra/wire.go` where fea | Highlight | `==marked==` | | Abbreviations | `*[HTML]: HyperText Markup Language` on its own line; every whole-word use expands | | Heading ids | automatic from the heading's text, or declared: `## Title {#stable-anchor}` | +| Task lists | `- [ ]` and `- [x]` in a list item, rendered as a **disabled** checkbox — static markup, nothing to click and nothing stored (ADR-0078) | Declare an id when the anchor must outlive the wording: a derived id changes with the text, so rewording a heading breaks every link to it (ADR-0066). Two headings with the same words get suffixed rather than @@ -311,10 +312,11 @@ An abbreviation's definition may sit anywhere in the file, before or after the u nothing itself. It applies to that document only: a term defined in a page does not reach an included fragment, which is parsed on its own bytes exactly as footnotes are. -Deliberately absent, so their absence is a decision rather than an oversight: **task lists** (a note-taking -affordance, not a publishing one), **linkify** (it rewrites an author's plain text into markup, which is the -line ADR-0034 draws), **CJK line breaking** (wrong script family — it does nothing for Bengali), and the -**GFM bundle**, which would drag the first two in with the tables it is wanted for. +Deliberately absent, so their absence is a decision rather than an oversight: **linkify** (it rewrites an +author's plain text into markup, which is the line ADR-0034 draws), **CJK line breaking** (wrong script +family — it does nothing for Bengali), and the **GFM bundle**, which would drag linkify in with the tables +it is wanted for. Task lists were on this list until ADR-0078 and are now enabled on their own, which is +the distinction: one named extension is not a bundle. **Footnotes inside an included file** get ids namespaced by that file — `_method-fn:1` rather than `fn:1` — because an include is converted on its own bytes (ADR-0038) and would otherwise number from one all over diff --git a/docs/decisions.md b/docs/decisions.md index f5f5765..8991a0d 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -908,7 +908,9 @@ Revisit if: someone wants the reconciliation *moment* recorded rather than the c different fact and belongs in the arc retro log. ## ADR-0058 — The Markdown dialect is CommonMark plus five, named once -Date: 2026-08-01 · Status: accepted +Date: 2026-08-01 · Status: accepted, except the task-list exclusion, which ADR-0078 supersedes. The refusal +of the GFM bundle and of linkify stands and is the load-bearing half. The Decision text below stands as +written — read the task-list sentence as the position that was held, not the one that holds. Decision: tables, footnotes, definition lists, strikethrough and automatic heading ids are enabled; task lists, linkify, CJK line breaking and the GFM bundle are refused. The list lives in `cmd/khosra/wire.go` beside the features, and `content-model.md` carries the authored form. Footnote ids inside an included file @@ -1302,3 +1304,23 @@ theme every time rather than only when templates changed, which is microseconds Expensive — `web.Handler`'s signature changed and twenty test construction sites moved with it, and a `Renderer` can no longer be handed around and updated, which no caller wanted anyway. Revisit if: reparsing the theme on every content change ever shows up in a profile. + +## ADR-0078 — Task lists are enabled, superseding ADR-0058's exclusion +Date: 2026-08-02 · Status: accepted (supersedes the task-list half of ADR-0058) +Decision: `extension.TaskList` joins the dialect. `- [ ]` and `- [x]` render as goldmark emits them, with a +disabled checkbox — static markup, no script, no interactivity. `extension.GFM` is still refused, and +linkify with it. +Why: ADR-0058 excluded task lists as "a note-taking affordance, not a publishing one". That reasoning was +about the wrong axis. A checklist inside a published technical piece — setup steps, a migration runbook, a +what-I-tried list — is publishing, and the reader benefit is the same whether or not the author also uses +checkboxes for private notes. The human asked for them with that use in mind, and no other mechanism here +expresses "this item is done" without the author hand-writing an entity. +The narrower refusal ADR-0058 also made is untouched and is the part that mattered: the GFM bundle stays +out, because it drags linkify in with the tables it is wanted for, and linkify rewrites an author's plain +text into markup — the line ADR-0034 draws. Enabling one named extension is not enabling a bundle. +Consequence: one line in `cmd/khosra/wire.go` and no counter moves — an upstream extension enabled in the +list is dialect rather than a feature of this engine (`state.md` counters, "does not count" column). The +checkbox is `disabled`, so a reader cannot tick it and nothing is stored; a theme that wants the list to +read as prose unbullets it in CSS, which the reference theme now does. +Revisit if: authors start using task lists for working notes inside published bundles, in which case the +answer is `extras/`, which already renders Markdown and is excluded from every listing — not a change here. diff --git a/docs/state.md b/docs/state.md index f4e8c35..fc62c1a 100644 --- a/docs/state.md +++ b/docs/state.md @@ -30,7 +30,7 @@ table owns. | `internal/ext/scaffold/` | writes one draft directory bundle into a site root through `os.Root`: never an overwrite | | `internal/ext/watch/` | polls `content/` and `templates/` on an interval it is given, ignores editor droppings, and reports a settled change (ADR-0022, ADR-0048, ADR-0056). `site.yaml` is deliberately not fingerprinted (ADR-0055) | | `internal/ext/check/` | third feature: validates a site root — what the engine worked around, broken internal links, missing titles and alt text, mixed series ordering, and calls left in the retired shortcode form (ADR-0059) | -| `cmd/khosra/wire.go` | the only list of enabled features (`extensions.md`), the `theme` function that builds the renderer this build ships (ADR-0072), and the Markdown dialect with it — tables, footnotes, definition lists, strikethrough (ADR-0058) | +| `cmd/khosra/wire.go` | the only list of enabled features (`extensions.md`), the `theme` function that builds the renderer this build ships (ADR-0072), and the Markdown dialect with it — tables, footnotes, definition lists, strikethrough, task lists (ADR-0058, ADR-0078) | | `internal/web/resolve.go` | URL → (key, lang, page, tag, feed, extras) or a canonical redirect | | `internal/web/extras.go` | the extras route: listing, one entry selected, or `?raw` bytes, all behind the bundle lookup | | `internal/web/asset.go` | files inside a bundle's own directory, looked up through the owning bundle so visibility can only ever inherit (ADR-0024) | @@ -47,8 +47,8 @@ key (ADR-0035) — a paginated listing per section, tag listings global and section-narrowed, sequence navigation and a series archive on any nested bundle, `static/` verbatim, a directory bundle's own files under its URL, generated derivatives under `/derived/`, Atom feeds per site, section and tag, a bundle's extras as a browsable tree, plus `/robots.txt` and `/sitemap.xml`. -Markdown is CommonMark plus tables, footnotes, definition lists, heading ids and the inline marks -(`~sub~`, `^sup^`, `==mark==`, `~~strike~~`, `*[TERM]:`), and nothing else (ADR-0058, ADR-0061, ADR-0062); HTML an author writes renders, because the site root is trusted (ADR-0060). Chrome text, dates and digits render in English or Bengali; authored text is untouched but for typographic +Markdown is CommonMark plus tables, footnotes, definition lists, heading ids, task lists and the inline marks +(`~sub~`, `^sup^`, `==mark==`, `~~strike~~`, `*[TERM]:`), and nothing else (ADR-0058, ADR-0061, ADR-0062, ADR-0078); HTML an author writes renders, because the site root is trusted (ADR-0060). Chrome text, dates and digits render in English or Bengali; authored text is untouched but for typographic smoothing (ADR-0034); line breaking is left to CSS (ADR-0045). This repo holds engine source only — the site root is external and passed with `khosra check` validates a site root and exits non-zero on anything that makes it wrong; `khosra new` scaffolds a draft bundle into one. A running server notices changes under `content/` and `templates/` by diff --git a/docs/surface.md b/docs/surface.md index 96c47c0..2c8e393 100644 --- a/docs/surface.md +++ b/docs/surface.md @@ -6,9 +6,9 @@ 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 — 305 lines + 292 test +## cmd/khosra — 308 lines + 295 test -check.go 45 · main.go 177 · new.go 42 · wire.go 41 +check.go 45 · main.go 177 · new.go 42 · wire.go 44 - check.go:16 func runCheck(args []string) - main.go:23 func main() diff --git a/examples/demo-site/content/pages/colophon.en.md b/examples/demo-site/content/pages/colophon.en.md index 50b57a3..faab265 100644 --- a/examples/demo-site/content/pages/colophon.en.md +++ b/examples/demo-site/content/pages/colophon.en.md @@ -27,3 +27,13 @@ without the copies drifting from it — each keeping the line numbers it really :::tip{title="Nothing is hidden"} Panels are `
`, which every browser opens without help. A reader with scripting off sees the same page. ::: + +## What this build does and does not do + +A task list is checked off in the source and rendered as a **disabled** checkbox — nothing here is +clickable, and nothing is stored (ADR-0078): + +- [x] Highlight code on the server, so no script parses anything in your browser +- [x] Draw icons from Unicode rather than shipping an icon font +- [x] Serve every panel above as plain `
` +- [ ] Ship a single byte of JavaScript on a page like this one diff --git a/internal/render/templates/theme.css b/internal/render/templates/theme.css index ee3e73f..e7ccf0e 100644 --- a/internal/render/templates/theme.css +++ b/internal/render/templates/theme.css @@ -14,6 +14,11 @@ header .site { font-weight: 600; margin: 0 0 0.25rem; } nav.sections a, nav.languages a, nav.tags a { margin-right: 0.75rem; } nav.sequence, nav.ends { display: flex; gap: 1rem; justify-content: space-between; margin: 1.5rem 0; } ul.extras, ol.archive { padding-left: 1.25rem; } +/* A task list is an ordinary
    whose items open with a checkbox — goldmark adds no class, so `:has` is + how a theme recognises one without the engine inventing markup for it (ADR-0078). A bullet beside a box + says the same thing twice. */ +ul:has(> li > input[type="checkbox"]) { list-style: none; padding-left: 0; } +li > input[type="checkbox"] { margin-right: 0.4em; } .kind { color: #6b6b6b; font-size: 0.85em; } .gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 0.75rem; } .gallery figure { margin: 0; }