From 950ca71a48c51b17b2354779c4b8d8595390dee8 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 7 Jun 2026 02:24:23 +0000 Subject: [PATCH] content: HTML page wrapper (page.sx) + 7 tests (455/455) Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/content/conformance.sh | 3 ++- lib/content/page.sx | 26 +++++++++++++++++++++++ lib/content/scoreboard.json | 5 +++-- lib/content/scoreboard.md | 3 ++- lib/content/tests/page.sx | 42 +++++++++++++++++++++++++++++++++++++ plans/content-on-sx.md | 8 ++++++- 6 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 lib/content/page.sx create mode 100644 lib/content/tests/page.sx diff --git a/lib/content/conformance.sh b/lib/content/conformance.sh index b29e41e8..7fc2e99e 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 markdown text section stats table validate store snapshot crdt crdt-store sync md-import fed) +SUITES=(block doc render api meta page markdown text section stats table validate store snapshot crdt crdt-store sync md-import fed) OUT_JSON="lib/content/scoreboard.json" OUT_MD="lib/content/scoreboard.md" @@ -47,6 +47,7 @@ run_suite() { (load "lib/content/section.sx") (load "lib/content/stats.sx") (load "lib/content/table.sx") +(load "lib/content/page.sx") (load "lib/content/markdown.sx") (load "lib/content/validate.sx") (load "lib/content/store.sx") diff --git a/lib/content/page.sx b/lib/content/page.sx new file mode 100644 index 00000000..37a797a6 --- /dev/null +++ b/lib/content/page.sx @@ -0,0 +1,26 @@ +;; content-on-sx — full HTML page wrapper. +;; +;; content/page composes the metadata + render layers into the shippable +;; artifact the blog serves: a minimal valid HTML5 document with an escaped +;; (from doc metadata, falling back to the id) and the rendered blocks +;; as the body. +;; +;; Requires (loaded by harness): doc.sx, render.sx (asHTML + htmlEscaped), +;; meta.sx (doc-title). + +(define ct-html-escape (fn (s) (str (st-send s "htmlEscaped" (list))))) + +(define + content/page-title + (fn (doc) (let ((t (doc-title doc))) (if (= t nil) (doc-id doc) t)))) + +(define + content/page + (fn + (doc) + (str + "<!doctype html><html><head><meta charset=\"utf-8\"><title>" + (ct-html-escape (content/page-title doc)) + "" + (asHTML doc) + ""))) diff --git a/lib/content/scoreboard.json b/lib/content/scoreboard.json index 0c2a11c9..9419d345 100644 --- a/lib/content/scoreboard.json +++ b/lib/content/scoreboard.json @@ -5,6 +5,7 @@ "render": {"pass": 42, "fail": 0}, "api": {"pass": 26, "fail": 0}, "meta": {"pass": 27, "fail": 0}, + "page": {"pass": 7, "fail": 0}, "markdown": {"pass": 20, "fail": 0}, "text": {"pass": 20, "fail": 0}, "section": {"pass": 25, "fail": 0}, @@ -19,7 +20,7 @@ "md-import": {"pass": 24, "fail": 0}, "fed": {"pass": 20, "fail": 0} }, - "total_pass": 448, + "total_pass": 455, "total_fail": 0, - "total": 448 + "total": 455 } diff --git a/lib/content/scoreboard.md b/lib/content/scoreboard.md index 8430f924..ce7265bb 100644 --- a/lib/content/scoreboard.md +++ b/lib/content/scoreboard.md @@ -9,6 +9,7 @@ _Generated by `lib/content/conformance.sh`_ | render | 42 | 0 | 42 | | api | 26 | 0 | 26 | | meta | 27 | 0 | 27 | +| page | 7 | 0 | 7 | | markdown | 20 | 0 | 20 | | text | 20 | 0 | 20 | | section | 25 | 0 | 25 | @@ -22,4 +23,4 @@ _Generated by `lib/content/conformance.sh`_ | sync | 14 | 0 | 14 | | md-import | 24 | 0 | 24 | | fed | 20 | 0 | 20 | -| **Total** | **448** | **0** | **448** | +| **Total** | **455** | **0** | **455** | diff --git a/lib/content/tests/page.sx b/lib/content/tests/page.sx new file mode 100644 index 00000000..fd89804b --- /dev/null +++ b/lib/content/tests/page.sx @@ -0,0 +1,42 @@ +;; Extension — full HTML page wrapper. + +(st-bootstrap-classes!) +(content/bootstrap!) + +(define + d + (doc-with-title + (doc-append (doc-empty "post") (mk-heading "h" 1 "Hi")) + "My Title")) + +(content-test + "page" + (content/page d) + "My Title

Hi

") + +(content-test + "page title escaped" + (content/page (doc-with-title (doc-empty "x") "A < B")) + "A < B") + +(content-test + "page falls back to id" + (content/page (doc-empty "fallback")) + "fallback") + +(content-test "page-title from meta" (content/page-title d) "My Title") +(content-test + "page-title fallback id" + (content/page-title (doc-empty "z")) + "z") + +(content-test + "page body reflects edits" + (content/page (doc-update d "h" "text" "Bye")) + "My Title

Bye

") + +(content-test + "page multi-block body" + (content/page + (doc-append (doc-with-title (doc-empty "p") "T") (mk-text "x" "para"))) + "T

para

") diff --git a/plans/content-on-sx.md b/plans/content-on-sx.md index e2e33eb4..f5da924d 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` → **448/448** (Phases 1–4 COMPLETE + 12 extensions: HTML/SX escaping, Markdown render+import, CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees, doc stats, table block) +`bash lib/content/conformance.sh` → **455/455** (Phases 1–4 COMPLETE + 13 extensions: HTML/SX escaping, Markdown render+import, CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees, doc stats, table block, HTML page wrapper) ## Ground rules @@ -88,9 +88,15 @@ 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] HTML page wrapper (`page.sx`: content/page, escaped title from metadata) ## Progress log +- 2026-06-07 — Extension: HTML page wrapper (`page.sx`). `content/page` composes + metadata + render into a minimal valid HTML5 document — escaped `` from + doc metadata (falling back to id) and the rendered blocks as the body. + `content/page-title`. The shippable artifact the blog serves. 7 tests; suite + 455/455. - 2026-06-07 — Extension: table block (`table.sx`). `CtTable` holds headers + rows (string lists); answers asHTML (escaped `<table>`), asSx, asText, and asMarkdown: (pipe table with dashed separator row) by folding rows×cells via