From 26d9aebc89e91e0bcf5042c7d9f6aaefce859f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 8 Jul 2026 23:18:03 +0200 Subject: [PATCH 1/5] Enforce the XHTML Strict content model for block/inline nesting The editor's flat-block model neither allowed everything the XHTML DTD allows nor forbade everything it forbids. Nesting decisions now flow from a single declarative source of truth: - content-model.xsl (new): 1:1 transcription of the XHTML 1.0 Strict DTD (Appendix A.1 entities + element declarations) and the Appendix B prohibitions, extended with HTML5 figure/figcaption; cm:* predicates consumed by edit, canonical and lint so verdicts cannot drift - canonical-xhtml.xsl: two passes - sanitize/normalize (content-aware div handling, run-wrapper and sectioning unwrap), then cm-normalize (blockquote stray runs wrapped in p, blocks hoisted out of inline-only elements RDFa-preservingly, ul/dl/tr child coercion, table fostering, a-in-a and pre exclusions); region-children block coercion at entry - edit.xsl: recursive editability init (text host vs container per the model, at any depth); mixed flow content like
  • text
  • gets an ephemeral p.rdfa-editor-run host and round-trips byte-identical; invalid host markup normalized once at load; paste nests blocks inside flow hosts instead of flattening; toolbar inserts follow the caret context (a list inserted in a cell nests in the cell) - authoring: Tab/Shift+Tab indent/outdent nested lists, progressive Enter/Backspace exits, quote toggle wraps/unwraps blockquote (block-only per Strict; RDFa-bearing quotes refuse), host-based block-type select, container-aware merges, flow-cell caret landings - lint-xhtml.xsl (new): invalid-nesting, stray-text, prohibited-nesting, unknown-element, surfaced live with the RDFa lint Storage contract change: stored bare-text blockquotes are rewritten to blockquote > p once (blockquote is block-only under Strict). New suites: tests/browser/nesting.mjs + authoring.mjs against tests/fixture-nesting.html; canonical fixtures 13-20, lint fixtures 11-12. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 19 +- CONTENT-MODEL-PLAN.md | 124 +++ rdfa-editor.css | 6 + src/canonical-xhtml.xsl | 309 ++++++- src/content-model.xsl | 189 +++++ src/edit.xsl | 769 ++++++++++++++++-- src/index.xsl | 3 + src/lint-xhtml.xsl | 60 ++ src/navigate.xsl | 11 +- src/tables.xsl | 15 +- tests/browser/authoring.mjs | 365 +++++++++ tests/browser/features.mjs | 2 +- tests/browser/hardening.mjs | 16 +- tests/browser/nesting.mjs | 171 ++++ tests/browser/run.mjs | 2 +- tests/canonical-driver.xsl | 17 + .../expected/canonical/01-chrome-strip.xhtml | 2 +- .../canonical/09-block-set-passthrough.xhtml | 2 +- .../canonical/13-blockquote-block-only.xhtml | 12 + .../canonical/14-nested-lists-roundtrip.xhtml | 21 + .../canonical/15-cell-flow-roundtrip.xhtml | 17 + .../canonical/16-inline-only-hoist.xhtml | 11 + .../17-list-dl-child-normalization.xhtml | 5 + .../canonical/18-run-wrapper-unwrap.xhtml | 6 + .../canonical/19-region-stray-inline.xhtml | 1 + .../expected/canonical/20-prohibitions.xhtml | 6 + tests/expected/lint/11-invalid-nesting.txt | 6 + .../expected/lint/12-valid-nesting-clean.txt | 0 tests/fixture-nesting.html | 61 ++ .../canonical/13-blockquote-block-only.xhtml | 12 + .../canonical/14-nested-lists-roundtrip.xhtml | 21 + .../canonical/15-cell-flow-roundtrip.xhtml | 17 + .../canonical/16-inline-only-hoist.xhtml | 6 + .../17-list-dl-child-normalization.xhtml | 5 + .../canonical/18-run-wrapper-unwrap.xhtml | 6 + .../canonical/19-region-stray-inline.xhtml | 1 + .../fixtures/canonical/20-prohibitions.xhtml | 5 + tests/fixtures/lint/11-invalid-nesting.xhtml | 8 + .../lint/12-valid-nesting-clean.xhtml | 13 + tests/lint-driver.xsl | 4 +- tests/normalize-xhtml.xsl | 3 +- tests/run-tests.sh | 2 +- 42 files changed, 2204 insertions(+), 127 deletions(-) create mode 100644 CONTENT-MODEL-PLAN.md create mode 100644 src/content-model.xsl create mode 100644 src/lint-xhtml.xsl create mode 100644 tests/browser/authoring.mjs create mode 100644 tests/browser/nesting.mjs create mode 100644 tests/canonical-driver.xsl create mode 100644 tests/expected/canonical/13-blockquote-block-only.xhtml create mode 100644 tests/expected/canonical/14-nested-lists-roundtrip.xhtml create mode 100644 tests/expected/canonical/15-cell-flow-roundtrip.xhtml create mode 100644 tests/expected/canonical/16-inline-only-hoist.xhtml create mode 100644 tests/expected/canonical/17-list-dl-child-normalization.xhtml create mode 100644 tests/expected/canonical/18-run-wrapper-unwrap.xhtml create mode 100644 tests/expected/canonical/19-region-stray-inline.xhtml create mode 100644 tests/expected/canonical/20-prohibitions.xhtml create mode 100644 tests/expected/lint/11-invalid-nesting.txt create mode 100644 tests/expected/lint/12-valid-nesting-clean.txt create mode 100644 tests/fixture-nesting.html create mode 100644 tests/fixtures/canonical/13-blockquote-block-only.xhtml create mode 100644 tests/fixtures/canonical/14-nested-lists-roundtrip.xhtml create mode 100644 tests/fixtures/canonical/15-cell-flow-roundtrip.xhtml create mode 100644 tests/fixtures/canonical/16-inline-only-hoist.xhtml create mode 100644 tests/fixtures/canonical/17-list-dl-child-normalization.xhtml create mode 100644 tests/fixtures/canonical/18-run-wrapper-unwrap.xhtml create mode 100644 tests/fixtures/canonical/19-region-stray-inline.xhtml create mode 100644 tests/fixtures/canonical/20-prohibitions.xhtml create mode 100644 tests/fixtures/lint/11-invalid-nesting.xhtml create mode 100644 tests/fixtures/lint/12-valid-nesting-clean.xhtml diff --git a/CLAUDE.md b/CLAUDE.md index 060685f..ba60b73 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Overview -RDFa-Editor is a prototype XHTML+RDFa authoring tool using client-side XSLT. Content lives in a `#content` container of structured blocks (p, h1–h3, ul/ol, blockquote, pre, figure, table), always editable Notion-style: typing within blocks, Enter splits, Backspace-at-start merges, toolbar for block types / inline formatting / lists / figures / tables / links, drag-handle reordering. Tables are composite blocks (structure locked; `td`/`th`/`caption` editable) inserted via a rows×cols dialog with an optional header row and caption; row/column toolbar controls, Tab/Shift+Tab cell traversal and Enter (step down a column) all grow the grid at its bottom edge. Right-click a text selection to annotate it with RDFa; right-click an existing annotation to edit or remove it. "Extract RDF" shows the page's triples; "Source" shows the canonical XHTML+RDFa serialization (all editing ephemera stripped). +RDFa-Editor is a prototype XHTML+RDFa authoring tool using client-side XSLT. Content lives in a `#content` container of structured blocks (p, h1–h3, ul/ol, blockquote, pre, figure, table), always editable Notion-style: typing within blocks, Enter splits, Backspace-at-start merges, toolbar for block types / inline formatting / lists / figures / tables / links, drag-handle reordering. Nesting follows the **XHTML Strict content model** (see content-model.xsl below): `blockquote` is a block *container* (`blockquote > p+` — bare text inside it is invalid and gets normalized; the toolbar quote toggle wraps/unwraps), `li`/`td`/`th`/`dd`/`figcaption` are `%Flow;` (mixed text AND nested blocks — Tab/Shift+Tab indent/outdent nested lists), `p`/`h1–h6`/`caption`/`dt` are inline-only. Tables are composite blocks (structure locked; `td`/`th`/`caption` editable) inserted via a rows×cols dialog with an optional header row and caption; row/column toolbar controls, Tab/Shift+Tab cell traversal and Enter (step down a column) all grow the grid at its bottom edge. Right-click a text selection to annotate it with RDFa; right-click an existing annotation to edit or remove it. "Extract RDF" shows the page's triples; "Source" shows the canonical XHTML+RDFa serialization (all editing ephemera stripped). ## Architecture @@ -23,15 +23,17 @@ Modules under `src/`: - **index.xsl** — entry point: `$vocab-hrefs` param, `main` template (async vocabulary prefetch via `ixsl:schedule-action/@document`, then overlay init), `local:document-uri()`. - **RDFa2RDFXML-v3.xsl** — RDFa 1.1 → RDF/XML extraction. **Pure XSLT, no `ixsl:`** — it runs headless via `xslt3` for the test suite. Strictly W3C-conformant (RDFa 1.1 processing rules, section 7.5, including chaining via typed resources and `rdfa:usesVocabulary`). Covers `@about`/`@typeof`/`@property`/`@content`/`@datatype`/`@resource`/`@href`/`@src`/`@prefix` (and `xmlns:*`)/`@vocab` with bare terms, `@lang`+`@xml:lang` inheritance, base-URI resolution (`about=""` = the document, `` honored), and exclusion of `head`/`script`/`style`/`[data-role='rendering']` subtrees. Out of scope (documented future work): `@rel`/`@rev`, `@inlist`, safe CURIEs, `@datetime`/`