content: deep tree editing (tree-edit.sx) + 17 tests (551/551)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m1s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m1s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
SUITES=(block doc render api meta page page-full markdown text section compose 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 stats table 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"
|
||||
@@ -46,6 +46,7 @@ run_suite() {
|
||||
(load "lib/content/text.sx")
|
||||
(load "lib/content/section.sx")
|
||||
(load "lib/content/compose.sx")
|
||||
(load "lib/content/tree-edit.sx")
|
||||
(load "lib/content/stats.sx")
|
||||
(load "lib/content/table.sx")
|
||||
(load "lib/content/data.sx")
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"text": {"pass": 20, "fail": 0},
|
||||
"section": {"pass": 25, "fail": 0},
|
||||
"compose": {"pass": 17, "fail": 0},
|
||||
"tree-edit": {"pass": 17, "fail": 0},
|
||||
"stats": {"pass": 17, "fail": 0},
|
||||
"table": {"pass": 15, "fail": 0},
|
||||
"data": {"pass": 21, "fail": 0},
|
||||
@@ -25,7 +26,7 @@
|
||||
"md-doc": {"pass": 12, "fail": 0},
|
||||
"fed": {"pass": 20, "fail": 0}
|
||||
},
|
||||
"total_pass": 534,
|
||||
"total_pass": 551,
|
||||
"total_fail": 0,
|
||||
"total": 534
|
||||
"total": 551
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| text | 20 | 0 | 20 |
|
||||
| section | 25 | 0 | 25 |
|
||||
| compose | 17 | 0 | 17 |
|
||||
| tree-edit | 17 | 0 | 17 |
|
||||
| stats | 17 | 0 | 17 |
|
||||
| table | 15 | 0 | 15 |
|
||||
| data | 21 | 0 | 21 |
|
||||
@@ -28,4 +29,4 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| md-import | 38 | 0 | 38 |
|
||||
| md-doc | 12 | 0 | 12 |
|
||||
| fed | 20 | 0 | 20 |
|
||||
| **Total** | **534** | **0** | **534** |
|
||||
| **Total** | **551** | **0** | **551** |
|
||||
|
||||
91
lib/content/tests/tree-edit.sx
Normal file
91
lib/content/tests/tree-edit.sx
Normal file
@@ -0,0 +1,91 @@
|
||||
;; Extension — deep tree editing (update/delete/insert into nested sections).
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(content/bootstrap!)
|
||||
(content-bootstrap-section!)
|
||||
|
||||
;; doc: top / sec[ a, inner[ b ] ]
|
||||
(define
|
||||
d
|
||||
(doc-append
|
||||
(doc-append (doc-empty "d") (mk-text "top" "T"))
|
||||
(mk-section
|
||||
"sec"
|
||||
(list
|
||||
(mk-text "a" "A")
|
||||
(mk-section "inner" (list (mk-text "b" "B")))))))
|
||||
|
||||
;; ── deep-update a nested block ──
|
||||
(define d1 (doc-deep-update d "b" "text" "Edited"))
|
||||
(content-test
|
||||
"deep-update nested"
|
||||
(str (blk-send (doc-deep-find d1 "b") "text"))
|
||||
"Edited")
|
||||
(content-test
|
||||
"deep-update immutable"
|
||||
(str (blk-send (doc-deep-find d "b") "text"))
|
||||
"B")
|
||||
(content-test
|
||||
"deep-update top-level"
|
||||
(str
|
||||
(blk-send
|
||||
(doc-deep-find (doc-deep-update d "top" "text" "X") "top")
|
||||
"text"))
|
||||
"X")
|
||||
(content-test
|
||||
"deep-update mid-section"
|
||||
(str
|
||||
(blk-send (doc-deep-find (doc-deep-update d "a" "text" "AA") "a") "text"))
|
||||
"AA")
|
||||
(content-test
|
||||
"deep-update preserves tree"
|
||||
(doc-tree-ids d1)
|
||||
(doc-tree-ids d))
|
||||
|
||||
;; ── deep-replace ──
|
||||
(define d2 (doc-deep-replace d "b" (mk-heading "b" 3 "H")))
|
||||
(content-test
|
||||
"deep-replace type"
|
||||
(blk-type (doc-deep-find d2 "b"))
|
||||
"heading")
|
||||
(content-test
|
||||
"deep-replace render"
|
||||
(asHTML d2)
|
||||
"<p>T</p><section><p>A</p><section><h3>H</h3></section></section>")
|
||||
|
||||
;; ── deep-delete ──
|
||||
(define d3 (doc-deep-delete d "b"))
|
||||
(content-test "deep-delete removes nested" (doc-deep-find d3 "b") nil)
|
||||
(content-test
|
||||
"deep-delete tree-ids"
|
||||
(doc-tree-ids d3)
|
||||
(list "top" "sec" "a" "inner"))
|
||||
(content-test "deep-delete immutable" (doc-tree-count d) 5)
|
||||
(content-test
|
||||
"deep-delete mid-section"
|
||||
(doc-tree-ids (doc-deep-delete d "a"))
|
||||
(list "top" "sec" "inner" "b"))
|
||||
(content-test
|
||||
"deep-delete top-level"
|
||||
(doc-tree-ids (doc-deep-delete d "top"))
|
||||
(list "sec" "a" "inner" "b"))
|
||||
|
||||
;; ── deep-insert-into a nested section ──
|
||||
(define d4 (doc-deep-insert-into d "inner" (mk-text "c" "C")))
|
||||
(content-test
|
||||
"insert-into nested"
|
||||
(doc-tree-ids d4)
|
||||
(list "top" "sec" "a" "inner" "b" "c"))
|
||||
(content-test
|
||||
"insert-into found"
|
||||
(str (blk-send (doc-deep-find d4 "c") "text"))
|
||||
"C")
|
||||
(content-test
|
||||
"insert-into outer section"
|
||||
(doc-tree-ids (doc-deep-insert-into d "sec" (mk-divider "dv")))
|
||||
(list "top" "sec" "a" "inner" "b" "dv"))
|
||||
(content-test "insert-into immutable" (doc-tree-count d) 5)
|
||||
(content-test
|
||||
"insert-into render"
|
||||
(asHTML d4)
|
||||
"<p>T</p><section><p>A</p><section><p>B</p><p>C</p></section></section>")
|
||||
96
lib/content/tree-edit.sx
Normal file
96
lib/content/tree-edit.sx
Normal file
@@ -0,0 +1,96 @@
|
||||
;; content-on-sx — deep tree editing.
|
||||
;;
|
||||
;; Mutate blocks anywhere in the nested tree (descending into CtSection children),
|
||||
;; complementing the top-level doc ops and the deep-find read path. All return
|
||||
;; new documents (immutable).
|
||||
;;
|
||||
;; Requires (loaded by harness): doc.sx, section.sx (section? / section-children /
|
||||
;; section-with-children / section-append).
|
||||
|
||||
;; map f over every block in the tree, replacing the one whose id matches.
|
||||
(define
|
||||
block-tree-update
|
||||
(fn
|
||||
(blocks id f)
|
||||
(map
|
||||
(fn
|
||||
(b)
|
||||
(if
|
||||
(= (blk-id b) id)
|
||||
(f b)
|
||||
(if
|
||||
(section? b)
|
||||
(section-with-children
|
||||
b
|
||||
(block-tree-update (section-children b) id f))
|
||||
b)))
|
||||
blocks)))
|
||||
|
||||
;; remove the block with id from anywhere in the tree.
|
||||
(define
|
||||
block-tree-delete
|
||||
(fn
|
||||
(blocks id)
|
||||
(map
|
||||
(fn
|
||||
(b)
|
||||
(if
|
||||
(section? b)
|
||||
(section-with-children
|
||||
b
|
||||
(block-tree-delete (section-children b) id))
|
||||
b))
|
||||
(filter (fn (b) (if (= (blk-id b) id) false true)) blocks))))
|
||||
|
||||
;; append a block into the children of the section with section-id.
|
||||
(define
|
||||
block-tree-insert-into
|
||||
(fn
|
||||
(blocks section-id block)
|
||||
(map
|
||||
(fn
|
||||
(b)
|
||||
(if
|
||||
(section? b)
|
||||
(if
|
||||
(= (blk-id b) section-id)
|
||||
(section-append b block)
|
||||
(section-with-children
|
||||
b
|
||||
(block-tree-insert-into (section-children b) section-id block)))
|
||||
b))
|
||||
blocks)))
|
||||
|
||||
;; ── document-level deep ops ──
|
||||
(define
|
||||
doc-deep-update
|
||||
(fn
|
||||
(doc id field value)
|
||||
(doc-with-blocks
|
||||
doc
|
||||
(block-tree-update
|
||||
(doc-blocks doc)
|
||||
id
|
||||
(fn (b) (blk-set b field value))))))
|
||||
|
||||
(define
|
||||
doc-deep-replace
|
||||
(fn
|
||||
(doc id newblock)
|
||||
(doc-with-blocks
|
||||
doc
|
||||
(block-tree-update (doc-blocks doc) id (fn (b) newblock)))))
|
||||
|
||||
(define
|
||||
doc-deep-delete
|
||||
(fn
|
||||
(doc id)
|
||||
(doc-with-blocks doc (block-tree-delete (doc-blocks doc) id))))
|
||||
|
||||
(define
|
||||
doc-deep-insert-into
|
||||
(fn
|
||||
(doc section-id block)
|
||||
(doc-with-blocks
|
||||
doc
|
||||
(block-tree-insert-into (doc-blocks doc) section-id block))))
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **534/534** (Phases 1–4 COMPLETE + 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, doc stats, table block, HTML page wrapper + SEO page, doc composition, portable data + wire serialization)
|
||||
`bash lib/content/conformance.sh` → **551/551** (Phases 1–4 COMPLETE + 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 tree editing, doc stats, table block, HTML page wrapper + SEO page, doc composition, portable data + wire serialization)
|
||||
|
||||
## Ground rules
|
||||
|
||||
@@ -92,11 +92,17 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
- [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)
|
||||
- [x] deep tree editing (`tree-edit.sx`: doc-deep-update/replace/delete/insert-into)
|
||||
- [x] portable data serialization (`data.sx`: content/to-data + from-data, round-trips tree)
|
||||
- [x] wire serialization (`wire.sx`: content/to-wire + from-wire, SX-text on the wire)
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-06-07 — Extension: deep tree editing (`tree-edit.sx`). `doc-deep-update`
|
||||
/ `doc-deep-replace` / `doc-deep-delete` / `doc-deep-insert-into` mutate blocks
|
||||
anywhere in the nested tree (descending into CtSection children), completing
|
||||
tree mutation to match the deep-find read path; all immutable. 17 tests; suite
|
||||
551/551.
|
||||
- 2026-06-07 — Extension: on-the-wire serialization (`wire.sx`).
|
||||
`content/to-wire` serialises a document to a transmittable SX-text string (data
|
||||
form + SX serializer); `content/from-wire` parses it back into a live document.
|
||||
|
||||
Reference in New Issue
Block a user