Section Editing & Nested Sections — Proposal

Status: proposal, 2026-08-04. Design worked out with John this session. Related: JSONHTL_SPEC (the "semantic document tree, like the DOM" design model), proposals/verb-taxonomy, gdata-server/todo ("section-aware edits").

Decision

Motivation — why a real tree

In the flat model (a list of blocks where a heading just implies hierarchy by position) the request "append a paragraph to the outer section, after its sub-sections" is inexpressible. Example:

## Discussion      <- target
Para A
Para B
### Causes         <- sub-heading
Para C
### Fixes
Para D
## Next Section

"At the outer level" (belongs to Discussion) forces placement BEFORE ### Causes — because anything after a sub-heading reads as part of that sub-heading. "Later" (after the sub-sections) forces placement after Para D, where it reads as part of ### Fixes — the opposite of outer level. You can have one or the other, not both, precisely because the hierarchy is implied, not real.

A real tree removes the contradiction: a section is a container that holds its own blocks AND its child sections, so a trailing paragraph can be an unambiguous direct child of the outer section, after the inner ones — exactly as an HTML <section> works, and exactly why CSS selectors are well-defined over the DOM.

The section block

A new base block type. title is the heading text (plain string, as heading.text is today); the rendered heading level is the nesting depth (a section renders its title as hN where N = base depth + nesting), so no explicit level is needed. content is an ordinary block list that may itself contain nested section blocks.

{"section": {
  "title": "Discussion",
  "content": [
    {"para": ["Para A"]},
    {"para": ["Para B"]},
    {"section": {"title": "Causes", "content": [{"para": ["Para C"]}]}},
    {"section": {"title": "Fixes",  "content": [{"para": ["Para D"]}]}},
    {"para": ["Concluding paragraph \u2014 unambiguously belongs to Discussion, after its sub-sections."]}
  ]
}}

This is the HTML <section><h/>…<section>…</section>…</section> shape. An optional explicit level may override depth-derived level for edge cases; normally omit it.

Coexistence & migration

Addressing — CSS-like selectors

Editing operations

Operate on a section's content list (a real array). Vocabulary mirrors Perl/Python list ops so it is obvious and consistent:

OpPerl/PythonEffect
section.pushpush / appendappend block(s) at end of content ("outer level, later" is now well-defined)
section.unshiftunshift / insert(0)prepend block(s) at start of content
section.poppopremove & return last block
section.shiftshift / pop(0)remove & return first block
section.insertsplice / insert(i)insert before/after a position or child selector
section.replacea[i]=xreplace a block at a position/selector
section.removedel / splicedelete a block (or range) at a position/selector
section.readreturn content as a scoped outline (index, type, title/preview, id)

Edge cases resolved

Impact / consumers

Per the Sync Rule, a structural change touches: JSONHTL_SPEC (add section as a base block; document level-by-depth), JSONHTL_SCHEMA (recursive block_section), gdata-server/linter (register + rules), notes_web.py and notes_browser.py (render section -> heading + recursed content), gdata_mcp_server.py (the section.* op family, mirroring table_op), notes_client.py, and agent writers.

Phased build plan

  1. Phase 0 — allow the block: add section to JSONHTL_SPEC/SCHEMA/linter; both renderers render it (accept both flat + nested). Non-breaking. Ship + observe.
  2. Phase 1 — normaliser flat->nested (tool + tests); optionally migrate a few notes to dogfood.
  3. Phase 2 — selector resolver (exact, :nth, nth-child/of-type) + core ops push/unshift/pop/shift/read via a section_op dispatcher (REST + MCP), keeping the ids sidecar in step.
  4. Phase 3 — insert/replace/remove with before/after + child selectors.
  5. Later — see Deferred. Observe behaviour before expanding (John).

Deferred (documented, not dropped)

Status / Progress

✅ Phase 0 (allow + render the section block) shipped 2026-08-04: JSONHTL_SPEC 0.4, JSONHTL_SCHEMA v4, gdata-server/linter v5; both HTML renderers and jsonhtl_md render sections; unit + live tested. ✅ Phase 1 (normaliser) shipped 2026-08-04: jsonhtl_sections.py (nest_content / flatten_content / normalise_note), round-trip validated against the whole public corpus (472/472 identical). Phases 2-3 (the section.* editing ops) not yet started.

created 2026-08-04  ·  updated 2026-08-04  ·  version 2  ·  tags jsonhtl, editing, proposal, dom