From c0ca2509d0feb4575eb7e71c1936e2f2c3f66343 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 7 Jun 2026 04:57:40 +0000 Subject: [PATCH] content: callout/admonition block (callout.sx) + 12 tests (657/657) Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/content/callout.sx | 49 ++++++++++++++++++++++++++++++++ lib/content/conformance.sh | 3 +- lib/content/scoreboard.json | 5 ++-- lib/content/scoreboard.md | 3 +- lib/content/tests/callout.sx | 55 ++++++++++++++++++++++++++++++++++++ lib/content/validate.sx | 10 +++++++ plans/content-on-sx.md | 8 +++++- 7 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 lib/content/callout.sx create mode 100644 lib/content/tests/callout.sx diff --git a/lib/content/callout.sx b/lib/content/callout.sx new file mode 100644 index 00000000..d9ef5a50 --- /dev/null +++ b/lib/content/callout.sx @@ -0,0 +1,49 @@ +;; content-on-sx — callout / admonition block. +;; +;; CtCallout holds a `kind` (note/warning/tip/…) and `text`. Self-contained: it +;; answers asHTML/asSx/asText/asMarkdown: so it composes with the render boundary +;; with no changes elsewhere. HTML text is htmlEscaped, SX text sxEscaped. +;; +;; Requires (loaded by harness): block.sx, doc.sx, render.sx (escapers); +;; markdown.sx / text.sx for those formats. + +(define + content-bootstrap-callout! + (fn + () + (begin + (st-class-define! "CtCallout" "CtBlock" (list "kind" "text")) + (ct-def-method! "CtCallout" "kind" "kind ^ kind") + (ct-def-method! "CtCallout" "text" "text ^ text") + (ct-def-method! "CtCallout" "type" "type ^ #callout") + (ct-def-method! + "CtCallout" + "asHTML" + "asHTML ^ ''") + (ct-def-method! + "CtCallout" + "asSx" + "asSx ^ '(aside :class \"callout callout-' , kind sxEscaped , '\" \"' , text sxEscaped , '\")'") + (ct-def-method! "CtCallout" "asText" "asText ^ text") + (ct-def-method! + "CtCallout" + "asMarkdown:" + "asMarkdown: nl ^ '> **' , kind , ':** ' , text") + true))) + +(define + mk-callout + (fn + (id kind text) + (st-iv-set! + (st-iv-set! + (st-iv-set! (st-make-instance "CtCallout") "id" id) + "kind" + kind) + "text" + text))) + +(define + callout? + (fn (b) (and (st-instance? b) (= (get b :class) "CtCallout")))) +(define callout-kind (fn (b) (st-send b "kind" (list)))) diff --git a/lib/content/conformance.sh b/lib/content/conformance.sh index 092223c4..67fea63a 100755 --- a/lib/content/conformance.sh +++ b/lib/content/conformance.sh @@ -15,7 +15,7 @@ if [ ! -x "$SX_SERVER" ]; then fi fi -SUITES=(block doc render api meta page page-full markdown text section compose tree-edit clone query toc anchor outline flatten transform normalize find-replace stats table data wire validate store snapshot crdt crdt-store sync md-import md-doc fed) +SUITES=(block doc render api meta page page-full markdown text section compose tree-edit clone query toc anchor outline flatten transform normalize find-replace stats table callout data wire validate store snapshot crdt crdt-store sync md-import md-doc fed) OUT_JSON="lib/content/scoreboard.json" OUT_MD="lib/content/scoreboard.md" @@ -58,6 +58,7 @@ run_suite() { (load "lib/content/find-replace.sx") (load "lib/content/stats.sx") (load "lib/content/table.sx") +(load "lib/content/callout.sx") (load "lib/content/data.sx") (load "lib/content/wire.sx") (load "lib/content/page.sx") diff --git a/lib/content/scoreboard.json b/lib/content/scoreboard.json index 2a803553..9db0743e 100644 --- a/lib/content/scoreboard.json +++ b/lib/content/scoreboard.json @@ -23,6 +23,7 @@ "find-replace": {"pass": 10, "fail": 0}, "stats": {"pass": 17, "fail": 0}, "table": {"pass": 15, "fail": 0}, + "callout": {"pass": 12, "fail": 0}, "data": {"pass": 21, "fail": 0}, "wire": {"pass": 11, "fail": 0}, "validate": {"pass": 23, "fail": 0}, @@ -35,7 +36,7 @@ "md-doc": {"pass": 12, "fail": 0}, "fed": {"pass": 20, "fail": 0} }, - "total_pass": 645, + "total_pass": 657, "total_fail": 0, - "total": 645 + "total": 657 } diff --git a/lib/content/scoreboard.md b/lib/content/scoreboard.md index 9eb912ec..31b5f85f 100644 --- a/lib/content/scoreboard.md +++ b/lib/content/scoreboard.md @@ -27,6 +27,7 @@ _Generated by `lib/content/conformance.sh`_ | find-replace | 10 | 0 | 10 | | stats | 17 | 0 | 17 | | table | 15 | 0 | 15 | +| callout | 12 | 0 | 12 | | data | 21 | 0 | 21 | | wire | 11 | 0 | 11 | | validate | 23 | 0 | 23 | @@ -38,4 +39,4 @@ _Generated by `lib/content/conformance.sh`_ | md-import | 38 | 0 | 38 | | md-doc | 12 | 0 | 12 | | fed | 20 | 0 | 20 | -| **Total** | **645** | **0** | **645** | +| **Total** | **657** | **0** | **657** | diff --git a/lib/content/tests/callout.sx b/lib/content/tests/callout.sx new file mode 100644 index 00000000..b4f64648 --- /dev/null +++ b/lib/content/tests/callout.sx @@ -0,0 +1,55 @@ +;; Extension — callout / admonition block. + +(st-bootstrap-classes!) +(content/bootstrap!) +(content-bootstrap-markdown!) +(content-bootstrap-text!) +(content-bootstrap-callout!) + +(define c (mk-callout "c" "warning" "Be careful")) + +;; ── identity ── +(content-test "callout is block" (block? c) true) +(content-test "callout? yes" (callout? c) true) +(content-test "callout type" (blk-type c) "callout") +(content-test "callout kind" (callout-kind c) "warning") + +;; ── render ── +(content-test + "callout html" + (asHTML c) + "") +(content-test + "callout sx" + (asSx c) + "(aside :class \"callout callout-warning\" \"Be careful\")") +(content-test "callout text" (asText c) "Be careful") +(content-test "callout markdown" (asMarkdown c) "> **warning:** Be careful") + +;; ── html escapes text ── +(content-test + "callout html escapes" + (asHTML (mk-callout "c" "note" "a < b")) + "") + +;; ── in a document ── +(define + d + (doc-append + (doc-append (doc-empty "d") (mk-heading "h" 1 "T")) + c)) +(content-test + "doc with callout html" + (asHTML d) + "

T

") + +;; ── validation ── +(content-test + "valid callout" + (content/valid? (doc-append (doc-empty "d") c)) + true) +(content-test + "bad callout kind flagged" + (content/issue-kinds + (doc-append (doc-empty "d") (mk-callout "c" 5 "x"))) + (list "field")) diff --git a/lib/content/validate.sx b/lib/content/validate.sx index 65be8fc4..c7d679f2 100644 --- a/lib/content/validate.sx +++ b/lib/content/validate.sx @@ -160,6 +160,16 @@ id (list? (blk-get b "rows")) "table rows must be a list"))) + ((= t "callout") + (append + (ct-field-issue + id + (string? (blk-get b "kind")) + "callout kind must be a string") + (ct-field-issue + id + (string? (blk-get b "text")) + "callout text must be a string"))) (else (list (ct-issue id "type" (str "unknown block type: " t)))))))) (define diff --git a/plans/content-on-sx.md b/plans/content-on-sx.md index 92a4b5eb..2223679f 100644 --- a/plans/content-on-sx.md +++ b/plans/content-on-sx.md @@ -19,7 +19,7 @@ injected adapter, not core. ## Status (rolling) -`bash lib/content/conformance.sh` → **645/645** (Phases 1–4 COMPLETE + ~28 extensions: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep editing + flatten, doc stats, table block, HTML page wrapper + SEO page, doc composition + id-remap, portable data + wire serialization, block query + transforms + find/replace, TOC + anchored headings + outline, normalization) +`bash lib/content/conformance.sh` → **657/657** (Phases 1–4 COMPLETE + ~29 extensions: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep editing + flatten, doc stats, table + callout blocks, HTML page wrapper + SEO page, doc composition + id-remap, portable data + wire serialization, block query + transforms + find/replace, TOC + anchored headings + outline, normalization) ## Ground rules @@ -89,6 +89,7 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─ - [x] nested block trees (`section.sx`: CtSection container, recursive render, deep-find) - [x] document statistics (`stats.sx`: word/char/block counts, reading time) - [x] table block (`table.sx`: CtTable, renders html/sx/text/md, validated) +- [x] callout block (`callout.sx`: CtCallout note/warning/tip, renders html/sx/text/md, validated) - [x] HTML page wrapper (`page.sx`: content/page, escaped title from metadata) - [x] SEO page (`page-full.sx`: content/page-full, lang + meta description from excerpt) - [x] document composition (`compose.sx`: concat/prepend/concat-all/wrap-section) @@ -107,6 +108,11 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─ ## Progress log +- 2026-06-07 — Extension: callout/admonition block (`callout.sx`). `CtCallout` + holds kind (note/warning/tip) + text; answers asHTML (`