content: list-card summary projection (summary.sx) + 14 tests (697/697)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 05:25:24 +00:00
parent e115af86d8
commit c18545ea08
6 changed files with 113 additions and 5 deletions

View File

@@ -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 move clone query toc anchor outline flatten transform normalize find-replace stats table callout media 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 move clone query toc anchor outline flatten transform normalize find-replace stats summary table callout media 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/normalize.sx")
(load "lib/content/find-replace.sx")
(load "lib/content/stats.sx")
(load "lib/content/summary.sx")
(load "lib/content/table.sx")
(load "lib/content/callout.sx")
(load "lib/content/media.sx")

View File

@@ -23,6 +23,7 @@
"normalize": {"pass": 11, "fail": 0},
"find-replace": {"pass": 10, "fail": 0},
"stats": {"pass": 17, "fail": 0},
"summary": {"pass": 14, "fail": 0},
"table": {"pass": 15, "fail": 0},
"callout": {"pass": 12, "fail": 0},
"media": {"pass": 15, "fail": 0},
@@ -38,7 +39,7 @@
"md-doc": {"pass": 12, "fail": 0},
"fed": {"pass": 20, "fail": 0}
},
"total_pass": 683,
"total_pass": 697,
"total_fail": 0,
"total": 683
"total": 697
}

View File

@@ -27,6 +27,7 @@ _Generated by `lib/content/conformance.sh`_
| normalize | 11 | 0 | 11 |
| find-replace | 10 | 0 | 10 |
| stats | 17 | 0 | 17 |
| summary | 14 | 0 | 14 |
| table | 15 | 0 | 15 |
| callout | 12 | 0 | 12 |
| media | 15 | 0 | 15 |
@@ -41,4 +42,4 @@ _Generated by `lib/content/conformance.sh`_
| md-import | 38 | 0 | 38 |
| md-doc | 12 | 0 | 12 |
| fed | 20 | 0 | 20 |
| **Total** | **683** | **0** | **683** |
| **Total** | **697** | **0** | **697** |

26
lib/content/summary.sx Normal file
View File

@@ -0,0 +1,26 @@
;; content-on-sx — list-card summary projection.
;;
;; content/summary returns a one-call projection for index/listing cards:
;; {:id :title :excerpt :words :reading-minutes :cover}
;; composing the metadata, text, stats and query layers. `cover` is the first
;; image's src (or nil).
;;
;; Requires (loaded by harness): doc.sx, meta.sx (doc-title), text.sx
;; (content/excerpt), stats.sx (word-count/reading), query.sx (select-type).
(define
content/summary-title
(fn (doc) (let ((t (doc-title doc))) (if (= t nil) (doc-id doc) t))))
(define
content/cover
(fn
(doc)
(let
((imgs (content/select-type doc "image")))
(if
(= (len imgs) 0)
nil
(str (blk-get (first imgs) "src"))))))
(define content/summary (fn (doc) {:id (doc-id doc) :reading-minutes (content/reading-minutes doc) :words (content/word-count doc) :title (content/summary-title doc) :excerpt (content/excerpt doc 160) :cover (content/cover doc)}))

View File

@@ -0,0 +1,74 @@
;; Extension — list-card summary projection.
(st-bootstrap-classes!)
(content/bootstrap!)
(content-bootstrap-text!)
(define
d
(doc-with-title
(doc-append
(doc-append
(doc-append (doc-empty "post") (mk-heading "h" 1 "Hello"))
(mk-text "p" "one two three four"))
(mk-image "img" "/cover.png" "cover"))
"My Post"))
;; image alt ("cover") is part of the plain-text projection, so it counts.
(define s (content/summary d))
(content-test "summary id" (get s :id) "post")
(content-test "summary title" (get s :title) "My Post")
(content-test
"summary excerpt"
(get s :excerpt)
"Hello one two three four cover")
(content-test "summary words" (get s :words) 6)
(content-test "summary reading" (get s :reading-minutes) 1)
(content-test "summary cover" (get s :cover) "/cover.png")
;; ── title falls back to id ──
(content-test
"summary title fallback"
(get
(content/summary (doc-append (doc-empty "x") (mk-text "p" "y")))
:title)
"x")
;; ── no image → cover nil ──
(content-test
"no cover"
(get
(content/summary (doc-append (doc-empty "x") (mk-text "p" "y")))
:cover)
nil)
(content-test "cover helper nil" (content/cover (doc-empty "e")) nil)
;; ── first image wins as cover ──
(define
d2
(doc-append
(doc-append (doc-empty "d") (mk-image "i1" "/a.png" "a"))
(mk-image "i2" "/b.png" "b")))
(content-test "first image cover" (content/cover d2) "/a.png")
;; ── empty doc ──
(define se (content/summary (doc-empty "e")))
(content-test "empty summary words" (get se :words) 0)
(content-test "empty summary excerpt" (get se :excerpt) "")
(content-test "empty summary cover" (get se :cover) nil)
;; ── excerpt truncates long content ──
(content-test
"excerpt truncated"
(>
(string-length
(get
(content/summary
(doc-append
(doc-empty "d")
(mk-text
"p"
"word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word")))
:excerpt))
100)
true)

View File

@@ -19,7 +19,7 @@ injected adapter, not core.
## Status (rolling)
`bash lib/content/conformance.sh`**683/683** (Phases 14 COMPLETE + ~31 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 + relative reorder, doc stats, table + callout + media blocks, 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`**697/697** (Phases 14 COMPLETE + ~32 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 + relative reorder, doc stats + summary, table + callout + media 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
@@ -91,6 +91,7 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
- [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] media block (`media.sx`: CtMedia video/audio, renders html/sx/text/md, validated)
- [x] list-card summary (`summary.sx`: content/summary — title/excerpt/words/reading/cover)
- [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)
@@ -110,6 +111,10 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
## Progress log
- 2026-06-07 — Extension: list-card summary (`summary.sx`). `content/summary`
returns `{:id :title :excerpt :words :reading-minutes :cover}` for index/listing
cards, composing metadata + text + stats + query (`content/cover` = first
image's src). Title falls back to id. 14 tests; suite 697/697.
- 2026-06-07 — Extension: video/audio media block (`media.sx`). `CtMedia` holds
kind (video/audio) + src; answers asHTML (`<video/audio src controls>`,
escaped), asSx, asText (""), asMarkdown: (`[kind](src)`). `mk-media`/`mk-video`/