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:`}},
|
absent: []string{"<script", `style="color:`}},
|
||||||
{what: "grouped panels are tabs, and need no script", path: "/pages/colophon/", code: 200,
|
{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"}},
|
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,
|
{what: "a lone panel is an expando", path: "/pages/about/", code: 200,
|
||||||
expect: []string{"<details>", "<summary>What is deliberately not shown</summary>"}},
|
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,
|
{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.Table,
|
||||||
extension.NewFootnote(extension.WithFootnoteIDPrefixFunction(shortcodes.FootnotePrefix)),
|
extension.NewFootnote(extension.WithFootnoteIDPrefixFunction(shortcodes.FootnotePrefix)),
|
||||||
extension.DefinitionList,
|
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(),
|
notation.New(),
|
||||||
shortcodes.New(partial),
|
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==` |
|
| Highlight | `==marked==` |
|
||||||
| Abbreviations | `*[HTML]: HyperText Markup Language` on its own line; every whole-word use expands |
|
| 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}` |
|
| 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
|
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
|
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
|
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.
|
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
|
Deliberately absent, so their absence is a decision rather than an oversight: **linkify** (it rewrites an
|
||||||
affordance, not a publishing one), **linkify** (it rewrites an author's plain text into markup, which is the
|
author's plain text into markup, which is the line ADR-0034 draws), **CJK line breaking** (wrong script
|
||||||
line ADR-0034 draws), **CJK line breaking** (wrong script family — it does nothing for Bengali), and the
|
family — it does nothing for Bengali), and the **GFM bundle**, which would drag linkify in with the tables
|
||||||
**GFM bundle**, which would drag the first two in with the tables it is wanted for.
|
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` —
|
**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
|
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.
|
different fact and belongs in the arc retro log.
|
||||||
|
|
||||||
## ADR-0058 — The Markdown dialect is CommonMark plus five, named once
|
## 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
|
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`
|
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
|
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
|
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.
|
`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.
|
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/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/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) |
|
| `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/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/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) |
|
| `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
|
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,
|
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`.
|
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
|
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); 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
|
(`~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
|
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`
|
`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
|
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: 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`.
|
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)
|
- check.go:16 func runCheck(args []string)
|
||||||
- main.go:23 func main()
|
- 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"}
|
:::tip{title="Nothing is hidden"}
|
||||||
Panels are `<details>`, which every browser opens without help. A reader with scripting off sees the same page.
|
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.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; }
|
nav.sequence, nav.ends { display: flex; gap: 1rem; justify-content: space-between; margin: 1.5rem 0; }
|
||||||
ul.extras, ol.archive { padding-left: 1.25rem; }
|
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; }
|
.kind { color: #6b6b6b; font-size: 0.85em; }
|
||||||
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 0.75rem; }
|
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 0.75rem; }
|
||||||
.gallery figure { margin: 0; }
|
.gallery figure { margin: 0; }
|
||||||
|
|||||||
Reference in New Issue
Block a user