enable task lists, superseding the decision that excluded them
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 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,9 @@ var exampleFeatures = []featureCase{
|
||||
absent: []string{"<script", `style="color:`}},
|
||||
{what: "grouped panels are tabs, and need no script", path: "/pages/colophon/", code: 200,
|
||||
expect: []string{`<details name="run" open>`, "<summary>Serve your own</summary>"}, absent: []string{"<script"}},
|
||||
{what: "a task list renders disabled checkboxes and nothing interactive", path: "/pages/colophon/", code: 200,
|
||||
expect: []string{`<input checked="" disabled="" type="checkbox"`, `<input disabled="" type="checkbox"`},
|
||||
absent: []string{"<script"}},
|
||||
{what: "a lone panel is an expando", path: "/pages/about/", code: 200,
|
||||
expect: []string{"<details>", "<summary>What is deliberately not shown</summary>"}},
|
||||
{what: "an admonition renders inside a comic chapter too", path: "/comics/the-long-monsoon/the-flood/", code: 200,
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+23
-1
@@ -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.
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
@@ -27,3 +27,13 @@ without the copies drifting from it — each keeping the line numbers it really
|
||||
:::tip{title="Nothing is hidden"}
|
||||
Panels are `<details>`, 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 `<details>`
|
||||
- [ ] Ship a single byte of JavaScript on a page like this one
|
||||
|
||||
@@ -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 <ul> 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; }
|
||||
|
||||
Reference in New Issue
Block a user