content: tree reparent — move-into section + promote (812/812)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
insert/move were top-level only; a block could never move into/out of a section. content/move-into (relocate to a section child at index, tree-wide) + content/promote (lift nested block to top level, subtree intact). Pure tree transforms like the rest of move.sx; cycle-safe (rejects moving a block into its own descendant). +13 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
;; content-on-sx — relative block reorder.
|
||||
;; content-on-sx — block reorder + reparent.
|
||||
;;
|
||||
;; Move a top-level block to just before / after another block by id — more
|
||||
;; ergonomic than the index-based doc-move. No-op if either id is missing.
|
||||
;; Immutable; composes the doc.sx list helpers.
|
||||
;; Relative reorder of top-level blocks (move-before/after/to-front/to-back by
|
||||
;; id) plus TREE reparenting: move a block into a section (content/move-into) or
|
||||
;; promote a nested block back out to the top level (content/promote). Reparent
|
||||
;; ops are tree-wide (the block may start anywhere) and cycle-safe — moving a
|
||||
;; block into its own descendant is rejected (no-op), so a section can never
|
||||
;; become its own ancestor. No-op if any id is missing. Immutable; composes the
|
||||
;; doc.sx list + tree helpers (doc-find-deep / ct-find-id / ct-remove-id /
|
||||
;; ct-replace-id / ct-insert-at).
|
||||
;;
|
||||
;; Requires (loaded by harness): doc.sx.
|
||||
|
||||
@@ -67,3 +72,57 @@
|
||||
(doc-with-blocks
|
||||
doc
|
||||
(append (ct-remove-id (doc-blocks doc) id) (list blk)))))))
|
||||
|
||||
;; ── reparent (tree-wide) ──
|
||||
;; move block `id` (from anywhere in the tree) to be a child of section
|
||||
;; `section-id` at index `i`. No-op if either id is missing, if id = section-id,
|
||||
;; or if section-id sits inside id's own subtree (would create a cycle).
|
||||
(define
|
||||
content/move-into
|
||||
(fn
|
||||
(doc id section-id i)
|
||||
(let
|
||||
((blk (doc-find-deep doc id)))
|
||||
(if
|
||||
(= blk nil)
|
||||
doc
|
||||
(if
|
||||
(= (doc-find-deep doc section-id) nil)
|
||||
doc
|
||||
(if
|
||||
(= id section-id)
|
||||
doc
|
||||
(if
|
||||
(= (ct-find-id (list blk) section-id) nil)
|
||||
(let
|
||||
((without (ct-remove-id (doc-blocks doc) id)))
|
||||
(doc-with-blocks
|
||||
doc
|
||||
(ct-replace-id
|
||||
without
|
||||
section-id
|
||||
(fn
|
||||
(sec)
|
||||
(let
|
||||
((ch (st-iv-get sec "children")))
|
||||
(if
|
||||
(list? ch)
|
||||
(st-iv-set! sec "children" (ct-insert-at ch i blk))
|
||||
sec))))))
|
||||
doc)))))))
|
||||
|
||||
;; promote block `id` (wherever it sits) out to the end of the top level. If it
|
||||
;; is already top-level this is a move-to-back. No-op if missing. A section keeps
|
||||
;; its whole subtree.
|
||||
(define
|
||||
content/promote
|
||||
(fn
|
||||
(doc id)
|
||||
(let
|
||||
((blk (doc-find-deep doc id)))
|
||||
(if
|
||||
(= blk nil)
|
||||
doc
|
||||
(doc-with-blocks
|
||||
doc
|
||||
(append (ct-remove-id (doc-blocks doc) id) (list blk)))))))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"section": {"pass": 25, "fail": 0},
|
||||
"compose": {"pass": 17, "fail": 0},
|
||||
"tree-edit": {"pass": 17, "fail": 0},
|
||||
"move": {"pass": 11, "fail": 0},
|
||||
"move": {"pass": 24, "fail": 0},
|
||||
"clone": {"pass": 10, "fail": 0},
|
||||
"query": {"pass": 20, "fail": 0},
|
||||
"toc": {"pass": 8, "fail": 0},
|
||||
@@ -43,7 +43,7 @@
|
||||
"md-doc": {"pass": 12, "fail": 0},
|
||||
"fed": {"pass": 20, "fail": 0}
|
||||
},
|
||||
"total_pass": 799,
|
||||
"total_pass": 812,
|
||||
"total_fail": 0,
|
||||
"total": 799
|
||||
"total": 812
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| section | 25 | 0 | 25 |
|
||||
| compose | 17 | 0 | 17 |
|
||||
| tree-edit | 17 | 0 | 17 |
|
||||
| move | 11 | 0 | 11 |
|
||||
| move | 24 | 0 | 24 |
|
||||
| clone | 10 | 0 | 10 |
|
||||
| query | 20 | 0 | 20 |
|
||||
| toc | 8 | 0 | 8 |
|
||||
@@ -46,4 +46,4 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| md-import | 38 | 0 | 38 |
|
||||
| md-doc | 12 | 0 | 12 |
|
||||
| fed | 20 | 0 | 20 |
|
||||
| **Total** | **799** | **0** | **799** |
|
||||
| **Total** | **812** | **0** | **812** |
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
;; Extension — relative block reorder.
|
||||
;; Extension — relative block reorder + tree reparent.
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(content/bootstrap!)
|
||||
(content-bootstrap-section!)
|
||||
|
||||
(define
|
||||
d
|
||||
@@ -61,3 +62,84 @@
|
||||
"render after move"
|
||||
(asHTML (content/move-after d "a" "c"))
|
||||
"<p>B</p><p>C</p><p>A</p>")
|
||||
|
||||
;; ── reparent: move a top-level block INTO a section ──
|
||||
(define
|
||||
nd
|
||||
(doc-append
|
||||
(doc-append (doc-empty "d") (mk-text "p" "P"))
|
||||
(mk-section "s" (list (mk-text "x" "X")))))
|
||||
(content-test
|
||||
"move-into: block leaves top level"
|
||||
(doc-ids (content/move-into nd "p" "s" 1))
|
||||
(list "s"))
|
||||
(content-test
|
||||
"move-into: block lands in section at index"
|
||||
(doc-tree-ids (content/move-into nd "p" "s" 1))
|
||||
(list "s" "x" "p"))
|
||||
(content-test
|
||||
"move-into at front of section"
|
||||
(doc-tree-ids (content/move-into nd "p" "s" 0))
|
||||
(list "s" "p" "x"))
|
||||
(content-test "move-into immutable" (doc-tree-ids nd) (list "p" "s" "x"))
|
||||
|
||||
;; ── reparent: move a NESTED block to a different section ──
|
||||
(define
|
||||
two
|
||||
(doc-append
|
||||
(doc-append (doc-empty "d") (mk-section "s1" (list (mk-text "n" "N"))))
|
||||
(mk-section "s2" (list (mk-text "y" "Y")))))
|
||||
(content-test
|
||||
"move-into across sections"
|
||||
(doc-tree-ids (content/move-into two "n" "s2" 1))
|
||||
(list "s1" "s2" "y" "n"))
|
||||
|
||||
;; ── promote: nested block out to top level (appended last) ──
|
||||
(content-test
|
||||
"promote nested to top level"
|
||||
(doc-tree-ids (content/promote two "n"))
|
||||
(list "s1" "s2" "y" "n"))
|
||||
(content-test
|
||||
"promote leaves section empty shell"
|
||||
(doc-ids (content/promote two "n"))
|
||||
(list "s1" "s2" "n"))
|
||||
(content-test
|
||||
"promote a whole section keeps its subtree"
|
||||
(doc-tree-ids
|
||||
(content/promote
|
||||
(doc-append
|
||||
(doc-empty "d")
|
||||
(mk-section "o" (list (mk-section "i" (list (mk-text "z" "Z"))))))
|
||||
"i"))
|
||||
(list "o" "i" "z"))
|
||||
|
||||
;; ── cycle guard: cannot move a section into its own descendant ──
|
||||
(define
|
||||
nest
|
||||
(doc-append
|
||||
(doc-empty "d")
|
||||
(mk-section
|
||||
"outer"
|
||||
(list (mk-section "inner" (list (mk-text "t" "T")))))))
|
||||
(content-test
|
||||
"move section into its own child is a no-op"
|
||||
(doc-tree-ids (content/move-into nest "outer" "inner" 0))
|
||||
(list "outer" "inner" "t"))
|
||||
(content-test
|
||||
"move block into itself is a no-op"
|
||||
(doc-tree-ids (content/move-into nest "inner" "inner" 0))
|
||||
(list "outer" "inner" "t"))
|
||||
|
||||
;; ── reparent no-ops on missing ids ──
|
||||
(content-test
|
||||
"move-into missing block no-op"
|
||||
(doc-tree-ids (content/move-into nd "zzz" "s" 0))
|
||||
(list "p" "s" "x"))
|
||||
(content-test
|
||||
"move-into missing section no-op"
|
||||
(doc-tree-ids (content/move-into nd "p" "zzz" 0))
|
||||
(list "p" "s" "x"))
|
||||
(content-test
|
||||
"promote missing no-op"
|
||||
(doc-tree-ids (content/promote nd "zzz"))
|
||||
(list "p" "s" "x"))
|
||||
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **799/799** (Phases 1–4 COMPLETE + ~34 extensions, hardened: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CvRDT flat + nested-tree + durable replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep editing + flatten + relative reorder, doc stats + summary + multi-doc index, 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` → **812/812** (Phases 1–4 COMPLETE + ~34 extensions, hardened: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CvRDT flat + nested-tree + durable replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep editing + flatten + relative reorder, doc stats + summary + multi-doc index, 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
|
||||
|
||||
@@ -106,6 +106,7 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
- [x] document outline (`outline.sx`: content/outline, nested heading tree)
|
||||
- [x] document flatten (`flatten.sx`: content/flatten, un-nest sections; inverse of wrap-section)
|
||||
- [x] relative reorder (`move.sx`: content/move-before/after/to-front/to-back by id)
|
||||
- [x] tree reparent (`move.sx`: content/move-into a section + content/promote out to top level; tree-wide, cycle-safe)
|
||||
- [x] document normalization (`normalize.sx`: content/normalize, drop empty blocks/sections)
|
||||
- [x] document sanitization (`sanitize.sx`: content/sanitize, drop invalid blocks tree-wide; validate's enforcement partner)
|
||||
- [x] global find/replace (`find-replace.sx`: content/find-replace across text-bearing blocks)
|
||||
@@ -136,6 +137,19 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-06-07 — Feature: tree reparent in move.sx. Until now insert/move were
|
||||
positional and top-level only, so a block could never be moved *into* a section
|
||||
or *out* of one — a real gap for editing nested documents. Added
|
||||
`content/move-into doc id section-id i` (relocate a block, from anywhere in the
|
||||
tree, to be a child of a section at index i) and `content/promote doc id`
|
||||
(lift a nested block out to the end of the top level; a moved section keeps its
|
||||
whole subtree). Both are pure tree transforms (consistent with the existing
|
||||
move family — not new op-log ops) built on doc-find-deep / ct-find-id /
|
||||
ct-remove-id / ct-replace-id. **Cycle-safe**: move-into no-ops when target is
|
||||
the block itself or sits inside the block's own subtree, so a section can never
|
||||
become its own ancestor. +13 move tests (into/promote/across-sections/empty-
|
||||
shell/whole-section-subtree/cycle-guard/missing-id no-ops). 812/812.
|
||||
|
||||
- 2026-06-07 — Feature: `content/sanitize` — the enforcement counterpart to
|
||||
`validate`. validate *reports* id/field issues; sanitize *removes* the
|
||||
offending blocks (tree-wide) so federated/imported input that failed
|
||||
|
||||
Reference in New Issue
Block a user