u2ui 1.5.2 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/el/ico/ico.js +5 -5
  2. package/el/rte/README.md +53 -13
  3. package/el/rte/manifest.json +19 -4
  4. package/el/rte/rte.css +31 -6
  5. package/el/rte/rte.js +174 -44
  6. package/el/rte0/README.md +37 -0
  7. package/el/rte0/manifest.json +33 -0
  8. package/el/rte0/rte0.css +9 -0
  9. package/el/rte0/rte0.js +56 -0
  10. package/el/system/styler.js +2 -0
  11. package/el/tooltip/tooltip.css +2 -2
  12. package/js/Placer/Placer.js +2 -1
  13. package/js/rte/AGENTS.md +142 -0
  14. package/js/rte/PLAN.md +488 -0
  15. package/js/rte/README.md +319 -25
  16. package/js/rte/blocks.js +7 -0
  17. package/js/rte/breaks.js +6 -0
  18. package/js/rte/classes.js +7 -0
  19. package/js/rte/docs/usage.md +845 -0
  20. package/js/rte/images.js +7 -0
  21. package/js/rte/link.js +8 -0
  22. package/js/rte/playground/README.md +95 -0
  23. package/js/rte/playground/hosts.html +518 -0
  24. package/js/rte/playground/index.html +824 -0
  25. package/js/rte/rte.js +56 -5
  26. package/js/rte/source.js +12 -0
  27. package/js/rte/src/browser/README.md +40 -0
  28. package/js/rte/src/browser/range-rect.js +82 -0
  29. package/js/rte/src/client/README.md +378 -0
  30. package/js/rte/src/client/blocks.js +38 -0
  31. package/js/rte/src/client/breaks.js +212 -0
  32. package/js/rte/src/client/classes.js +48 -0
  33. package/js/rte/src/client/editor.js +461 -0
  34. package/js/rte/src/client/external-input.js +47 -0
  35. package/js/rte/src/client/history.js +13 -0
  36. package/js/rte/src/client/images.js +275 -0
  37. package/js/rte/src/client/link.js +572 -0
  38. package/js/rte/src/client/marks.js +25 -0
  39. package/js/rte/src/client/source.js +182 -0
  40. package/js/rte/src/client/structure.js +27 -0
  41. package/js/rte/src/client/tables.js +162 -0
  42. package/js/rte/src/client/unstyle.js +32 -0
  43. package/js/rte/src/command/README.md +517 -0
  44. package/js/rte/src/command/block-boundary.js +43 -0
  45. package/js/rte/src/command/block-style.js +120 -0
  46. package/js/rte/src/command/commands.js +158 -0
  47. package/js/rte/src/command/delete.js +112 -0
  48. package/js/rte/src/command/edit.js +72 -0
  49. package/js/rte/src/command/element.js +45 -0
  50. package/js/rte/src/command/enter.js +175 -0
  51. package/js/rte/src/command/fragment.js +76 -0
  52. package/js/rte/src/command/insert.js +52 -0
  53. package/js/rte/src/command/list.js +284 -0
  54. package/js/rte/src/command/mark.js +583 -0
  55. package/js/rte/src/command/pending-marks.js +216 -0
  56. package/js/rte/src/command/table.js +207 -0
  57. package/js/rte/src/command/unstyle.js +248 -0
  58. package/js/rte/src/config/README.md +96 -0
  59. package/js/rte/src/config/config.js +127 -0
  60. package/js/rte/src/core/README.md +41 -0
  61. package/js/rte/src/core/core.js +156 -0
  62. package/js/rte/src/history/README.md +83 -0
  63. package/js/rte/src/history/history.js +283 -0
  64. package/js/rte/src/input/README.md +155 -0
  65. package/js/rte/src/input/external-input.js +130 -0
  66. package/js/rte/src/input/input-pipeline.js +414 -0
  67. package/js/rte/src/mark/README.md +219 -0
  68. package/js/rte/src/mark/dom-adapter.js +100 -0
  69. package/js/rte/src/mark/mark.js +133 -0
  70. package/js/rte/src/mark/standard.js +76 -0
  71. package/js/rte/src/model/README.md +71 -0
  72. package/js/rte/src/model/content-model.js +213 -0
  73. package/js/rte/src/model/html/README.md +51 -0
  74. package/js/rte/src/model/html/html-model.js +90 -0
  75. package/js/rte/src/normalize/execute/README.md +33 -0
  76. package/js/rte/src/normalize/execute/repair-executor.js +129 -0
  77. package/js/rte/src/normalize/normalizer/README.md +48 -0
  78. package/js/rte/src/normalize/normalizer/normalizer.js +141 -0
  79. package/js/rte/src/normalize/repair/README.md +70 -0
  80. package/js/rte/src/normalize/repair/repair-planner.js +208 -0
  81. package/js/rte/src/sanitize/README.md +105 -0
  82. package/js/rte/src/sanitize/native.js +43 -0
  83. package/js/rte/src/sanitize/policy.js +202 -0
  84. package/js/rte/src/selection/README.md +39 -0
  85. package/js/rte/src/selection/map/README.md +53 -0
  86. package/js/rte/src/selection/map/point-map.js +307 -0
  87. package/js/rte/src/selection/ownership/README.md +32 -0
  88. package/js/rte/src/selection/ownership/ownership.js +42 -0
  89. package/js/rte/src/selection/point/README.md +34 -0
  90. package/js/rte/src/selection/point/point.js +83 -0
  91. package/js/rte/src/selection/range/README.md +38 -0
  92. package/js/rte/src/selection/range/edit-range.js +223 -0
  93. package/js/rte/src/selection/snapshot.js +64 -0
  94. package/js/rte/src/source/README.md +64 -0
  95. package/js/rte/src/source/source.js +193 -0
  96. package/js/rte/src/surface/README.md +36 -0
  97. package/js/rte/src/surface/content.js +16 -0
  98. package/js/rte/src/surface/surface.js +87 -0
  99. package/js/rte/src/transaction/README.md +35 -0
  100. package/js/rte/src/transaction/transaction.js +56 -0
  101. package/js/rte/src/ui/README.md +255 -0
  102. package/js/rte/src/ui/chrome.js +214 -0
  103. package/js/rte/src/ui/handles.js +179 -0
  104. package/js/rte/src/ui/place.js +25 -0
  105. package/js/rte/src/ui/toolbar.js +283 -0
  106. package/js/rte/src/unstyle/README.md +103 -0
  107. package/js/rte/src/unstyle/unstyle.js +140 -0
  108. package/js/rte/tables.js +7 -0
  109. package/js/rte/unstyle.js +8 -0
  110. package/js/rte0/README.md +37 -0
  111. package/js/rte0/rte.js +7 -0
  112. package/js/{rte → rte0}/src/behavior.js +0 -1
  113. package/js/{rte → rte0}/src/events.js +0 -1
  114. package/js/{rte → rte0}/src/range.js +0 -2
  115. package/js/{rte → rte0}/src/ui.items.js +0 -2
  116. package/js/{rte → rte0}/src/ui.js +0 -1
  117. package/package.json +1 -1
  118. package/u2/Element.js +16 -4
  119. package/u2/enhance.js +2 -1
  120. package/u2/plan-scoped-refactoring.md +133 -304
  121. package/u2/projects.json +18 -2
  122. package/u2/tools/update.repos.json.js +1 -1
  123. package/u2/utils.js +0 -16
  124. package/el/qrcode/package.json +0 -22
  125. /package/js/{rte → rte0}/old/NodeCleaner.js +0 -0
  126. /package/js/{rte → rte0}/old/Rte/Rte.mjs +0 -0
  127. /package/js/{rte → rte0}/old/Rte/Rte.ui.items.mjs +0 -0
  128. /package/js/{rte → rte0}/old/Rte/Rte.ui.mjs +0 -0
  129. /package/js/{rte → rte0}/old/Rte/crossbrowser.mjs +0 -0
  130. /package/js/{rte → rte0}/old/Rte/font/tinymce.svg +0 -0
  131. /package/js/{rte → rte0}/old/Rte/font/tinymce.woff +0 -0
  132. /package/js/{rte → rte0}/old/Rte/helpers.mjs +0 -0
  133. /package/js/{rte → rte0}/old/Rte/htmlparser.mjs +0 -0
  134. /package/js/{rte → rte0}/old/Rte/index.mjs +0 -0
  135. /package/js/{rte → rte0}/old/Rte/main.css +0 -0
  136. /package/js/{rte → rte0}/old/old.rte.js +0 -0
  137. /package/js/{rte → rte0}/old/range-selection.old.js +0 -0
  138. /package/js/{rte → rte0}/src/fixes.js +0 -0
  139. /package/js/{rte → rte0}/src/rte.js +0 -0
  140. /package/js/{rte → rte0}/src/ui/items/ai.js +0 -0
  141. /package/js/{rte → rte0}/src/ui/items/code.js +0 -0
  142. /package/js/{rte → rte0}/src/ui/items/undo.js +0 -0
  143. /package/js/{rte → rte0}/src/util/htmlparser.js +0 -0
  144. /package/js/{rte → rte0}/src/util/indentHtml.js +0 -0
@@ -0,0 +1,142 @@
1
+ # RTE Agent Instructions
2
+
3
+ These instructions apply to this directory and all descendants.
4
+
5
+ ## Project status — important
6
+
7
+ RTE is new and not used in production or by downstream consumers yet. There
8
+ is no backward-compatibility obligation. Prefer the clean final API and remove
9
+ superseded names or designs instead of adding aliases, deprecations, shims, or
10
+ legacy layers. Existing behavior still needs tests, but compatibility with an
11
+ earlier RTE draft is not a reason to keep a weaker design.
12
+
13
+ ## Start here
14
+
15
+ - Read [`README.md`](./README.md) for the architecture and requirements.
16
+ - Read [`PLAN.md`](./PLAN.md) for implemented phases and the next responsibility.
17
+ - Read the README beside every responsibility you change.
18
+ - Treat `../rte0` as a read-only behavioral reference. Never modify it or any
19
+ other file outside `rte` while working on RTE.
20
+
21
+ ## Priorities
22
+
23
+ - Keep changes focused, compact, readable, and internally consistent.
24
+ - Prefer a small general solution to a collection of special cases. RTE is a
25
+ generic engine and must support use cases that were not anticipated.
26
+ - Treat reported examples as regression cases for a general rule. Do not ship
27
+ tag-, class-, fixture-, or browser-specific behavior when the underlying
28
+ concept can be expressed by configuration, the content model, or a reusable
29
+ primitive.
30
+ - KISS and YAGNI apply, but every contract must remain composable, replaceable,
31
+ and extensible.
32
+ - Shipped defaults are useful starting points, never fixed policy. Applications
33
+ may replace them.
34
+ - Fix obvious, local, undisputed defects encountered in the discussed area.
35
+ Do not expand work into subjective or speculative cleanup.
36
+ - Preserve existing comments and debugging functions unless their removal was
37
+ explicitly requested.
38
+
39
+ ## Architecture
40
+
41
+ - Use native `contenteditable`, `Selection`, `Range`, Input Events, and modern
42
+ Web APIs. Do not build a parallel document model.
43
+ - Never use `document.execCommand()`.
44
+ - Keep one shared core per selection context as the normal architecture. A core
45
+ may coordinate multiple editable surfaces and multiple roaming or static UIs.
46
+ - Mutable state belongs to its core, surface, transaction, or module instance.
47
+ Module globals are only for immutable constants and stateless helpers.
48
+ - Nested editable hosts are isolation boundaries for events, selections,
49
+ traversal, normalization, and commands.
50
+ - DOM mutations owned by the editor go through transactions and explicit point
51
+ mapping. Preserve forward and backward selection intent.
52
+ - Keep responsibilities narrow. Content validity, repair planning, mechanical
53
+ mutation, selection mapping, input routing, commands, history, sanitizing,
54
+ browser policies, and UI remain separate modules.
55
+ - Later layers depend on public contracts, never private implementation details.
56
+ - Browser workarounds must be feature-detected, isolated near the affected
57
+ primitive, and covered by a named regression test. Do not branch on user-agent
58
+ strings.
59
+ - Security sanitizing and structural normalization are separate policies.
60
+ Never pass untrusted HTML to unsafe DOM sinks.
61
+ - Scope normalization to the smallest affected subtree and expand only when a
62
+ repair crosses that boundary. It must converge, be idempotent, and preserve
63
+ meaningful content.
64
+
65
+ ## Configuration
66
+
67
+ - Give every module safe, useful, host-aware defaults without requiring setup.
68
+ - Prefer inheritable CSS custom properties for serializable behavior. Use
69
+ JavaScript configuration for functions, policy objects, and other values CSS
70
+ cannot express naturally.
71
+ - Different editing hosts have different semantics. Lists, inline-only hosts,
72
+ tables, generic blocks, atomic content, and nested editors must not be forced
73
+ through one generic block rule.
74
+ - Use current browser standards; compatibility with obsolete browsers is not a
75
+ goal. Modern CSS, including nesting, is allowed.
76
+
77
+ ## Code style
78
+
79
+ - Code and code comments are English.
80
+ - Use standard ESM: `import`/`export`, `const`/`let`, classes, and functions. No
81
+ IIFEs, `var`, prototype assignment, or accidental globals. Assign to
82
+ `globalThis` only for deliberate compatibility or test instrumentation.
83
+ - Use clear, established, natural, preferably short names. One concept gets one
84
+ term throughout the project.
85
+ - Avoid redundant API names: context already carries meaning (`module.add()`,
86
+ not `module.addModule()`). Prefer symmetric names for symmetric operations.
87
+ - Keep control flow compact with small helpers and short early returns. Do not
88
+ compress code until its intent becomes harder to see.
89
+ - Place related code together. Add a file only for a real responsibility,
90
+ shared use, or separate testability—not merely to reduce line count.
91
+ - Remove middle-man helpers that only forward arguments without adding policy,
92
+ validation, normalization, or composition.
93
+ - Do not hardcode application-specific tags, classes, commands, or policies in
94
+ generic primitives.
95
+ - Use braces for multi-line and non-trivial control flow. Keep the local style
96
+ consistent when changing existing code.
97
+ - Do not run broad automatic formatters. Never run `deno fmt` in this workspace.
98
+
99
+ ## HTML and CSS
100
+
101
+ - Keep production presentation independent from the engine. UI modules consume
102
+ editor state but do not become part of the core.
103
+ - Configure behavior through documented `--u2-rte-*` properties with good
104
+ fallbacks.
105
+ - Keep the playground compact and diagnostic. It must call production modules,
106
+ never contain a second implementation of editor behavior.
107
+ - Escape or sanitize user-controlled HTML with the selected sanitizing policy.
108
+ Parsing a trusted test fixture is not a substitute for a safe external-input
109
+ path.
110
+
111
+ ## Documentation and tests
112
+
113
+ - Every production file has a dedicated comprehensive test file and focused
114
+ documentation in its responsibility README.
115
+ - Every responsibility README documents purpose, public contract, invariants,
116
+ browser considerations, and concrete TODOs.
117
+ - Update documentation and `PLAN.md` in the same change whenever a contract or
118
+ implementation status changes. Do not describe planned behavior as existing.
119
+ - Tests cover normal behavior, boundaries, invalid input, cleanup, teardown,
120
+ nesting, direction, cancellation, event order, and state isolation where
121
+ relevant.
122
+ - A browser quirk becomes a minimal failing regression test before it becomes a
123
+ policy.
124
+ - Tests leave no fixtures, selections, listeners, active cores, or other state
125
+ behind.
126
+ - Do not weaken assertions, add timing guesses, or hide failures behind
127
+ browser-specific skips. Test the stable contract and isolate genuinely
128
+ engine-specific behavior.
129
+ - Run `/u2/js/rte/tests/` through HTTP. A revision is cross-browser verified
130
+ only when that exact revision passes in current Chromium, Firefox, and WebKit.
131
+ - Use `/u2/js/rte/playground/` for visual inspection of normalization, mapping,
132
+ selection, and input behavior.
133
+
134
+ ## Before handing off
135
+
136
+ - Review the diff for unrelated changes, redundant code, naming consistency,
137
+ cleanup, and stale documentation.
138
+ - Ensure every changed production responsibility has matching tests and docs.
139
+ - Report the exact browser revisions or engines actually tested. Never infer a
140
+ cross-browser pass from one engine.
141
+ - Mention changes outside the discussed area prominently, with reason and
142
+ effect.
package/js/rte/PLAN.md ADDED
@@ -0,0 +1,488 @@
1
+ # RTE implementation plan
2
+
3
+ Each phase ends with documentation, dedicated tests for every production file,
4
+ real-browser verification, and no known failing invariant. Later phases may
5
+ refine earlier contracts, but may not bypass their responsibility boundaries.
6
+
7
+ The working `../rte0` is a behavioral reference throughout the project. Existing
8
+ behavior, especially host-specific editing, selection handling, and browser
9
+ fixes, is first captured as black-box tests. RTE then preserves or deliberately
10
+ improves it through smaller responsibilities; structural weaknesses in the old
11
+ implementation are not mistaken for behavioral failures.
12
+
13
+ ## Resume work
14
+
15
+ Phases 0–3 provide the core, selection/range primitives, content model, scoped
16
+ normalization, playground, and input pipeline. The command layer, the prevented
17
+ input path, and Enter close the phase-3 gap between configuration and behavior.
18
+ Phase 4 has started with immutable mark types, serializable values, equivalence,
19
+ directional conflicts, deterministic sets, removal, and replaceable DOM
20
+ adapters. Generic non-collapsed range commands now apply and remove one mark,
21
+ including reusable inline elements and neutral-span cleanup, without
22
+ `execCommand()`. Selection and caret state, toggle, and pending ordinary text
23
+ input are implemented. Adapters can explicitly remove semantic wrappers while
24
+ preserving unrelated attributes, and the standard HTML adapters for bold,
25
+ italic, underline, strike, code, and links exercise the whole path. Pending
26
+ overrides now carry through native IME composition without
27
+ intercepting its mutations. Complete mark sets now resolve exclusions, remove
28
+ configured values absent from an exact target, and apply their canonical result
29
+ inside one mapped operation.
30
+
31
+ The 201-test mark-algebra baseline was confirmed in current Chromium, Firefox,
32
+ and WebKit. The 233-test pending-mark baseline passes in current Chromium,
33
+ Firefox, and WebKitGTK through GNOME Web. The 361-test baseline was confirmed in
34
+ Chromium, Firefox, and WebKit. The current runner adds empty list
35
+ exit, context-aware nested-list Enter, mixed generic-block repair, the roaming
36
+ toolbar, semantic wrapper removal, ready-made bold, the convention client with
37
+ optional command modules and block-style values, heading-aware Enter,
38
+ content-model-driven block merging in both deletion directions, focus
39
+ dismissal, collapsed range geometry, and one shared structural element policy
40
+ for normalization, commands, and toolbar choices. The current lifecycle and
41
+ visible-break extension, sanitizing policy/native adapter, selection-only
42
+ toolbar mode, staged Unstyle policy/command, mapped context-aware fragment
43
+ replacement, fail-closed rich paste/drop composition, and mapped post-native
44
+ presentation cleanup and composition-aware pending marks raise the verified
45
+ runner to 369 tests. Complete mark-set coverage raises the current runner to
46
+ 375 tests. Nested canonical mark coverage raises the current runner to 379
47
+ tests. Standard HTML mark coverage raises the current runner to 381 tests.
48
+ State-based history — baseline entries, transaction-grouped recording, path
49
+ addressed selection restoration, coalescing, branch and limit handling, and its
50
+ commands — raises the current runner to 398 tests. Lists, their nesting levels,
51
+ model-placed element insertion, and the standard mark and structure convention
52
+ modules raise the current runner to 427 tests. Atomic-block deletion,
53
+ selection-owned history commands, and the HTML source responsibility with its
54
+ dialog module raise the current runner to 452 tests. Value marks and the
55
+ contextual link editor raise the current runner to 470 tests. The CSS-declared
56
+ content classes shared by the style control, the sanitizer, and presentation
57
+ cleanup raise the current runner to 486 tests. One shared placement policy and
58
+ its coverage raise it to 490 tests. The full remove-format ladder and
59
+ registry-owned shortcuts raise it to 500 tests. Depth-independent wrapper
60
+ removal, a canonical level that does something, and paste cleanup that covers
61
+ what arrived raise it to 508 tests. The attribute policy on native imports
62
+ and a strict import element policy raise it to 513 tests, confirmed in Chrome
63
+ 152 and Firefox 154. Import aliases raise it to 519 tests, and the table
64
+ structure commands with their contextual handles to 538, and the image frame
65
+ with its attribute command to 554, all of it drawn in one shadow root per
66
+ editor; whole-content remove-format and the presence/availability rule raise it
67
+ to 565. The playground now configures its prototype surface live from a panel
68
+ built out of the registered modules, so every host property can be tried without
69
+ editing the page.
70
+ WebKit verification of that revision is pending.
71
+
72
+ Remove-format now runs a ladder that does not stop at the application's own
73
+ presentation: six presentation rungs in `Unstyle` (styles, presentational
74
+ attributes, foreign classes, formatting wrappers, declared content classes, and
75
+ the remaining semantic inline elements) plus a seventh `blocks` rung in the
76
+ command that reduces lists, tables, quotes, and headings to the host's default
77
+ block. The action is unavailable only when the selection is already plain text,
78
+ and a collapsed caret reaches the whole content rather than nothing.
79
+ Keyboard shortcuts moved from toolbar controls to the command registry, so a key
80
+ works whether or not a control is on screen, and `Tab`/`Shift+Tab` reach the
81
+ list-nesting commands only inside a list.
82
+
83
+ What may arrive is now its own question. `--u2-rte-import-elements` defaults to
84
+ the `@content` preset — headings, text, lists, tables, media and the text-level
85
+ semantics — while `--u2-rte-elements` stays what a host tolerates in content it
86
+ already owns. Neither can widen the sanitize policy.
87
+
88
+ An element the import list does not carry, but whose meaning a listed one does,
89
+ is replaced rather than dropped: `<b>` becomes `<strong>`, `<i>` becomes `<em>`.
90
+ Nothing did that afterwards — the bold mark recognizes `<b>`, but only a mark
91
+ command ever makes an element canonical — so a strict list would otherwise have
92
+ turned every pasted emphasis into plain text.
93
+
94
+ A native paste now meets the attribute policy. Nothing is parsed there — the
95
+ browser inserts its own payload — so it was the one import path without an
96
+ allowlist, and layout ids, tracking attributes and inline styles came straight
97
+ through. The pipeline applies `SanitizePolicy.clean()` to the nodes that
98
+ arrived, before presentation cleanup and structural repair, so an id removed in
99
+ the first stage is what lets the third dissolve the wrapper carrying it.
100
+ `--u2-rte-import-attributes: keep` opts a host out.
101
+
102
+ Pasted content is now cleaned where it landed. The cleanup scope was derived
103
+ from the caret, which sits at the *end* of a paste, so a pasted document was
104
+ only ever repaired in its last block — the reason legacy markup survived intact
105
+ even after the planner learned to remove it. The nodes that arrived are already
106
+ tracked, and now decide the scope.
107
+
108
+ Redundant markup is now removed at any depth. A neutral generic block was only
109
+ ever reshaped as a direct child of the root, so legacy documents kept their
110
+ nested bare `div`s — valid HTML, which is exactly why the content model alone
111
+ never removed them. `canonical` also stopped being a synonym for `structural`:
112
+ it now dissolves a generic inline wrapper that carries nothing.
113
+
114
+ The same work made normalization measurably cheaper. Measured in Chrome 152 as
115
+ the best of five runs against the previous revision: `ContentModel.allows()`
116
+ 14.8 → 3.2 µs, one paragraph normalized 213 → 85 µs, a clean 264-element article
117
+ 18.7 → 7.1 ms. The model no longer allocates a string per rule lookup and skips
118
+ the ancestor-exclusion walk for children nothing can exclude; the normalizer no
119
+ longer asks the executor to confirm the plans that change nothing, and no longer
120
+ re-validates every element the walk just collected.
121
+
122
+ A review pass consolidated four duplicated concepts into single definitions
123
+ (`isEditingBoundary`, `elementOf`, `indexOf`, `narrow`), removed a duplicated
124
+ availability check and a second placement policy, and made `edit.model` resolve
125
+ lazily. Measured in Chrome 152: an availability check that does not consult the
126
+ content model dropped from 10.0 to 1.8 µs, the list scan from 31.4 to 21.0 µs,
127
+ and one toolbar refresh from 490 to 437 µs. A lazy `config()` was measured and
128
+ rejected: building eight accessor descriptors costs more than parsing the values
129
+ eagerly.
130
+ Treat the number shown by `/u2/js/rte/tests/` as authoritative.
131
+
132
+ ## 0. Foundation
133
+
134
+ Status: implemented. The 106-test foundation and policy suite passed in current
135
+ Chromium, Firefox, and WebKit. Later phase tests extend the same browser runner.
136
+
137
+ - CSS configuration with semantic host defaults.
138
+ - One core per selection context and multiple editable surfaces.
139
+ - Direction-preserving selection snapshots based on live ranges.
140
+ - Synchronous transactions, dirty-node collection, lifecycle events, and
141
+ nested transaction reuse.
142
+ - Dependency-free browser test runner.
143
+
144
+ Exit criteria:
145
+
146
+ - Static checks pass.
147
+ - Foundation suites pass in current Chromium, Firefox, and WebKit.
148
+ - Focus, selection, nested editors, cancellation, and teardown leak no state.
149
+
150
+ ## 1. Positions and ranges
151
+
152
+ Status: ownership, live points, range splitting, text/block traversal,
153
+ containment, and explicit operation mapping are implemented. The browser
154
+ baseline passed in all target engines. Generated cases cover range traversal and
155
+ the content-preserving point-map operations; `remove`, `replace`, and `move`
156
+ still need an oracle of their own.
157
+
158
+ - Define logical DOM points with affinity at text, element, and atomic bounds.
159
+ - Map points through split, wrap, unwrap, replace, move, merge, and remove.
160
+ - Traverse selected content by blocks and editable boundaries.
161
+ - Preserve forward/backward selections and collapsed carets through every
162
+ primitive operation.
163
+
164
+ Tests emphasize every boundary offset, empty nodes, `<br>`, replaced endpoints,
165
+ atomic nodes, nested hosts, and generated forward/backward ranges.
166
+
167
+ ## 2. Content model and normalization
168
+
169
+ Status: the immutable policy engine, standards-oriented HTML rules, pure repair
170
+ planning, mapped repair execution, scoped fixed-point normalization, and a
171
+ visual playground are implemented. Generated cases assert convergence,
172
+ idempotence, preserved text, and valid output across levels and host tags.
173
+
174
+ - Model permitted parents/children from HTML content categories plus editor
175
+ policy overrides.
176
+ - Implement minimal, structural, and canonical normalization as convergent
177
+ rules: repair, lift, split, unwrap, convert, merge, and remove.
178
+ - Preserve meaningful attributes, classes, and styles while removing neutral
179
+ wrappers and empty metadata.
180
+ - Reduce transaction touches to the smallest stable dirty scopes; expand only
181
+ when a repair crosses the boundary.
182
+ - Make normalization idempotent and canonical output deterministic.
183
+
184
+ The useful ideas in the old `NodeCleaner`—allow-lists, modernization, removal of
185
+ empty metadata, block-only wrapper detection, and Unicode normalization—become
186
+ separate policies. Computed-style comparison, prototype mutation, and unrelated
187
+ whole-tree cleanup are not carried forward.
188
+
189
+ ## 3. Input pipeline
190
+
191
+ Status: post-native event classification, CSS cleanup triggers, live target
192
+ ranges, local normalization scope, composition deferral, nested-host isolation,
193
+ selection mapping, disposal, command routing with native text data for
194
+ prevented input, and replacement by an already prepared DOM fragment are
195
+ implemented. `ExternalInput` consumes rich paste/drop `dataTransfer` and the
196
+ native target range, applies the selected sanitizer and optional Unstyle
197
+ policy, and invokes mapped fragment insertion while failures stay fail-closed.
198
+ Without an explicitly selected sanitizer, native paste/drop remains
199
+ browser-owned; the pipeline removes classes and styles only from observed added
200
+ roots before structural normalization while preserving elements that predate
201
+ the input.
202
+ Collapsed backward and forward deletion at a
203
+ mergeable block boundary are routed explicitly; ordinary character and
204
+ selected-range key deletion remain native. Contextual plain-text and quotation
205
+ import are next.
206
+
207
+ - Route `beforeinput`, `input`, composition, paste, drop, delete, and Enter by
208
+ `inputType` and host policy.
209
+ - Let native editing proceed only where its result is interoperable and covered
210
+ by postconditions; otherwise prevent it and execute an explicit command.
211
+ - Sanitize external fragments before insertion, then normalize only the fragment
212
+ and insertion neighborhood.
213
+ - Give lists, inline hosts, tables, generic blocks, and atomic content semantic
214
+ default behavior that CSS can override.
215
+
216
+ Tests use trusted browser input where automation allows it and direct event/
217
+ command contracts for exhaustive edge cases.
218
+
219
+ ## 4. Marks and inline commands
220
+
221
+ Status: started. Mark values define equivalence, directional exclusion,
222
+ canonical ordering, and removal. Replaceable adapters parse existing elements
223
+ and render canonical wrappers for semantic tags, classes, attributes, styles,
224
+ and custom policies. Generic commands apply and remove one configured mark over
225
+ arbitrary non-collapsed ranges, preserve selection direction, reuse suitable
226
+ inline elements, join adjacent canonical wrappers, clean up neutral spans, and
227
+ derive selection or structural caret state for toggle. A closed adapter
228
+ universe can now report and set exact canonical mark sets while resolving type
229
+ conflicts in the same transaction. Redundant nesting is removed, canonical
230
+ single-child wrappers follow mark rank, and newly exposed siblings merge to a
231
+ fixed point without crossing meaningful or atomic boundaries. Formatting other
232
+ than the standard HTML policies remains application-defined.
233
+ `PendingMarks` routes ordinary `insertText` through mapped input and applies the
234
+ same caret overrides to a live native composition range only after IME ends.
235
+ The ready-made adapters cover bold, italic, underline, strike, code, and links.
236
+ They render one canonical tag, remove their semantic wrappers explicitly, and
237
+ preserve unrelated attributes. Links use a small structured value without
238
+ coupling URL policy to mark representation.
239
+
240
+ - Apply, remove, and toggle over arbitrary ranges without
241
+ `document.execCommand()`.
242
+ - Support semantic elements, class tokens, attributes, style declarations, and
243
+ custom mark adapters through one algebra.
244
+ - Split boundaries minimally; merge equivalent siblings; eliminate redundant,
245
+ conflicting, or empty wrappers.
246
+ - Keep staged remove-format separate as Unstyle because it spans multiple mark
247
+ representations. A future link-control module owns URL entry and validation.
248
+
249
+ Tests combine partial text, multiple blocks, nested marks, overlapping removal,
250
+ backward selections, atomic content, repeated toggles, and exact undo.
251
+
252
+ ## 5. Structural commands
253
+
254
+ Status: started. The command registry, the `Edit` execution context, the
255
+ `PointMap.split()` primitive, and the Enter/line-break commands are implemented.
256
+ Enter also exits an empty item from a nested list without crossing its surface,
257
+ splits content-model text blocks such as headings, and creates the configured
258
+ default block after a non-default text block's exact end. Backspace and Delete
259
+ merge compatible content-model blocks at either boundary and treat every caret
260
+ representation inside an empty block identically.
261
+ `BlockStyles` converts one or several known text-block wrappers, preserves
262
+ selection and unrelated attributes, reports mixed state, and supports custom
263
+ selector/tag/write/clear definitions without treating layout blocks as text
264
+ styles.
265
+ `Lists` owns one closed group of container elements and takes item elements from
266
+ the model's `defaultChild`, so no command names `li`. `toggle(tag)` converts
267
+ blocks into items, converts an existing list of another kind, and lifts items
268
+ back out; a partly selected list is split so only the selected run changes, and
269
+ a result that meets a list of its own kind joins it. `indent` and `outdent` move
270
+ a run between nesting levels and claim `formatIndent` and `formatOutdent`.
271
+ `insertNode(create, inputTypes)` inserts one prepared element where the content
272
+ model accepts it, splitting the caret's block only that far, and covers the
273
+ horizontal rule.
274
+
275
+ - Extend paragraph and heading conversion to loose pre-normalization text.
276
+ - Blockquotes, links, tables, and configurable atomic nodes.
277
+ - Preserve ordered-list numbering across a split or lift.
278
+ - Delete a non-collapsed selection before splitting or inserting, instead of
279
+ leaving that case native.
280
+
281
+ ## 6. History
282
+
283
+ Status: implemented as a state model. `History` records one entry per change of
284
+ one surface, storing the content as a cloned fragment with a path-addressed
285
+ selection that survives content replacement. A `MutationObserver` covers every
286
+ origin, so native typing, commands, paste, drop, and unrelated application
287
+ scripts all enter history without the engine owning their mutations.
288
+ Transactions supply the grouping boundaries: the `input` trigger coalesces into
289
+ one entry per interval, every other trigger records a discrete step before and
290
+ after itself. `undo`/`redo` are ordinary commands claiming `historyUndo` and
291
+ `historyRedo`; the input pipeline routes Ctrl/Command+Z and +Y from `keydown`
292
+ because a browser stops reporting those input types once its own stack no longer
293
+ matches replaced content. The convention client installs one history per
294
+ rich-text surface and contributes both toolbar controls.
295
+
296
+ - Store diffs instead of whole content states once transactions record
297
+ reversible operations, keeping the state model for unowned mutations.
298
+ - Group composition deliberately rather than relying on its mutations alone.
299
+ - Expose the coalescing interval and an off switch as `--u2-rte-history`.
300
+ - Decide whether the entry limit should be measured in bytes rather than
301
+ entries, and verify the cost on large documents.
302
+
303
+ ## 7. Sanitizing and serialization
304
+
305
+ Status: started. `SanitizePolicy` now defines one immutable element, global and
306
+ per-element attribute, URL-protocol, comment, and data-attribute contract. The
307
+ first `NativeSanitizer` adapter parses into a detached fragment exclusively
308
+ through `Element.setHTML()`, intersects surface elements without broadening the
309
+ security policy, and fails explicitly when that safe sink is unavailable. It
310
+ has no unsafe fallback. Native paste/drop needs no parser fallback because its
311
+ payload stays browser-owned. `Source` now serializes a surface and parses source text back through the same
312
+ policy. A canonical serializer and contextual parsing remain open.
313
+ The independent immutable `Unstyle` policy supplies ordered presentation
314
+ levels to both the selection command and safely parsed external fragments, so
315
+ paste/drop will not grow a second formatting-cleanup list.
316
+ `insertFragment` now replaces a selection with such a prepared fragment through
317
+ mapped removal and insertion. Its content-model lifting handles inline, block,
318
+ list, and nested-link contexts without browser editing commands.
319
+ `ExternalInput` composes the three responsibilities for applications that
320
+ explicitly replace rich paste/drop. The default cross-engine path instead
321
+ cleans the browser's inserted DOM without reading its HTML payload.
322
+
323
+ - Resolve the surface's structural element policy once and use it as the upper
324
+ bound for cleanup, command availability, paste/drop sanitizing, and toolbar
325
+ choices. A sanitizer may narrow that policy but never broaden it.
326
+ `--u2-rte-classes` declares the class names a host treats as content. It is one
327
+ declaration with three consumers: the style control offers exactly those names,
328
+ `SanitizePolicy.clean({classes})` keeps only those from external HTML, and
329
+ `Unstyle` narrows the class attribute to them and leaves a wrapper carrying one,
330
+ because a declared class is content rather than presentation.
331
+
332
+ - Keep additional sanitizer adapters application-supplied behind the same
333
+ policy contract unless a general engine use case requires one.
334
+ - Define allowed global/per-element attributes, URL protocols, class/data
335
+ policy, inline-style policy, and comments separately from allowed elements.
336
+ - Keep security sanitizing separate from structural normalization.
337
+ - Serialize canonical HTML without editor-only markers or browser debris.
338
+ - Test hostile fragments, contextual parsing, mutation-XSS regressions, URL
339
+ attributes, custom elements, pasted office markup, and policy narrowing.
340
+
341
+ ## 8. UI adapters
342
+
343
+ Status: started. The optional roaming `Toolbar` binds application-owned markup
344
+ to the active surface's resolved command registry, reflects availability and
345
+ boolean/mixed state, keeps saved selections across UI focus, supports simple
346
+ Ctrl/Command shortcuts, hides after focus leaves surface and toolbar, and
347
+ delegates placement and presentation. Application-supplied manual popovers and
348
+ the convention toolbar now enter and leave the browser top layer with the same
349
+ visibility contract. `--u2-rte-toolbar-when: selection` optionally requires a
350
+ non-collapsed saved selection.
351
+
352
+ The first batteries-included convention-client prototype is implemented: one
353
+ side-effect import plus `--u2-rte` and `--u2-rte-toolbar` lazily wires standard
354
+ Enter/input behavior, Bold, and one shared default toolbar. It allocates no UI
355
+ before the first active rich-text surface and leaves `plaintext-only` native.
356
+ The explicit `rte.js` API remains the composable engine layer. Optional
357
+ extensions install atomically into current and future surfaces through
358
+ `Editor.add()`, contribute controls, and are removed everywhere through
359
+ `Editor.delete()`. The optional `blocks.js` module exercises this with a
360
+ command-valued Paragraph/H1/H2/H3 select and replaceable custom style policy.
361
+ The optional `breaks.js` probe adds one editor-wide `setup()` lifetime, one
362
+ per-surface `attach()` lifetime, a non-transactional view command, and a
363
+ presentation-only top-layer overlay without inserting markers into editable
364
+ HTML. Optional `unstyle.js` contributes a staged action whose first applicable
365
+ level changes only selected content; levels and labels are replaceable.
366
+
367
+ - Publish command availability and active/mixed state as observable editor
368
+ state.
369
+ - Extend roaming controls beyond pressed buttons and command-valued selects to
370
+ menus and application-owned custom controls. A select's `options` may already
371
+ be a function of the surface, so a control's choices can come from the host's
372
+ configuration; the client refills it before the toolbar is shown.
373
+ - Presence follows the configuration and availability the selection: a control
374
+ this editor does not offer is absent, one that cannot act right now is
375
+ disabled. A toolbar that rearranged itself as the caret moved would move its
376
+ targets out from under the pointer. `--u2-rte-toolbar-unavailable: hide`
377
+ trades that away where a host prefers a toolbar of only what it can do.
378
+ - Implement static UIs bound to one surface; allow several UIs and command
379
+ subsets to coexist.
380
+ - Verify reusable placement policy across carets, ranges, writing modes, and
381
+ viewport edges.
382
+ - Exercise the lifecycle with another contextual UI before adding dependency,
383
+ ordering, or asynchronous setup concepts.
384
+
385
+ ### Optional extension probes
386
+
387
+ These are concrete architecture tests, not commitments to one large plugin
388
+ framework. Each extension must install explicitly, add no resources while it is
389
+ absent, work for current and future surfaces, and release everything through
390
+ `dispose()` when it or its editor is removed.
391
+
392
+ 1. **Visible line breaks — prototype implemented.** It is non-destructive but needs
393
+ extension-owned presentation and per-surface state. Provide a CSS default,
394
+ an optional toolbar toggle, and an always-on mode that needs no button. Do
395
+ not insert marker nodes into editable HTML. Chromium does not render
396
+ generated content on `<br>`, so the prototype uses one scheduled top-layer
397
+ overlay without a mutation observer. Firefox and WebKit still need visual
398
+ confirmation.
399
+ 2. **HTML source editor — implemented.** `Source` serializes a surface by
400
+ walking its DOM and reports where the selection lands in that text, so the
401
+ view opens at the caret without inserting marker nodes. Formatting breaks a
402
+ level into lines only where whitespace cannot be significant, and writing
403
+ removes exactly those breaks again, so a read/write round trip is lossless.
404
+ Writing always parses through the sanitizer and never assigns `innerHTML`.
405
+ `source.js` adds the top-layer dialog, whose text area is wrapped in
406
+ `<u2-code>` so the source is highlighted where that element is defined and
407
+ plain where it is not; the engine itself gains no dependency. Mapping the
408
+ dialog's own selection back into the DOM and a canonical serializer remain
409
+ open.
410
+ 3. **Link editor — implemented.** `valueMark(adapter)` generalizes the value
411
+ half: one command creates, changes, and removes a mark whose value is
412
+ content, and at a caret it acts on the whole mark it sits on, so an existing
413
+ link's address can be changed without selecting its text. `link.js` adds the
414
+ contextual form, which follows the caret the way the table and image handles
415
+ do: it appears at a link, anchored on it, and goes when the caret leaves, so
416
+ the toolbar control is left with creating one. Its address field is plain text
417
+ because native url validation rejects relative paths, fragments, and
418
+ application schemes; protocol and attribute policy stays with the sanitizer.
419
+ The form applies as it is typed rather than on an Apply button, marking each
420
+ run as ongoing input so history keeps an address as one step, and it edits the
421
+ one link it appeared on. `normalize`, `suggest`, and `complete` leave what an
422
+ address means to the application, the last of them filling a list of entries
423
+ that may carry markup. Editing the link text itself remains open.
424
+ 4. **Table tools — implemented as commands.** `Tables` groups insertion,
425
+ row and column insertion and deletion, and table removal. It names no
426
+ section, row, or cell tag: each comes from the model's `defaultChild`, one
427
+ level apart. Every change is an ordinary mapped mutation, so it is one undo
428
+ step and the caret lands where the edit happened. Spanning cells make the
429
+ index-counted actions unavailable rather than shifting the wrong cells.
430
+ `tables.js` puts them on the table itself: row handles down its left edge and
431
+ column handles along its top, each lined up with the cell the caret is in,
432
+ repositioned from selection, change, input, scroll and resize rather than
433
+ from a timer. Cell movement and span-aware column arithmetic remain open.
434
+ 5. **Image sizing — implemented.** `selectedElement()` and
435
+ `elementAttributes()` make the one element a selection covers addressable and
436
+ give it a value command over a fixed set of attributes; `images.js` adds the
437
+ frame and the pointer interaction. The drag moves the frame only and the size
438
+ is written once on release, so a resize is one undo step and the overlay owns
439
+ no size policy: `width` and `height` are what the sanitize policy allows on an
440
+ image. What is resizable is a selector. Its three handles sit on the trailing
441
+ edges only, because the flow holds an image's top and start edges: the corner
442
+ keeps the proportion, the two edges change one measurement each. An alt-text
443
+ editor and keyboard resizing remain open, as does a hook for regenerating the
444
+ file server-side rather than letting the browser stretch it.
445
+
446
+ All roaming and contextual extension UI must be created in the core's
447
+ `Document` or `ShadowRoot`, use the browser top layer through Popover or Dialog
448
+ where appropriate, and share one placement policy.
449
+
450
+ Everything the convention client draws lives in one `Chrome`: a single shadow
451
+ root per editor holding the toolbar, the contextual handles, the link form and
452
+ the source dialog. An editor is chrome inside someone else's document and has to
453
+ survive their `button {}` rule; one encapsulated root also means the application
454
+ sees a single element instead of one per piece of UI. There is no `::part`
455
+ surface on purpose — it would make the chrome's internal structure a public
456
+ contract, and an application that wants different chrome builds its own on the
457
+ commands, which `Toolbar` has always supported by binding markup it is given.
458
+ The chrome now follows the active surface through the closest modal dialog,
459
+ open popover, or fullscreen boundary and returns to its original root when that
460
+ boundary closes. Native inertness makes descendant mounting mandatory; core
461
+ retention keeps the temporary UI child out of focus deactivation, Source, and
462
+ History even when the top-layer element is itself the editing host.
463
+
464
+ The handles inside it are one shared `Handles` component that owns no editor
465
+ concept, so anything with a rectangle can use it, inside this engine or outside
466
+ it: given a document instead of a shadow root it brings its own. Shadow-root style ownership,
467
+ focus transitions, disposal, and coexistence of multiple UIs are release-gate
468
+ tests, not application-specific details.
469
+
470
+ ## 9. Browser policies and release gate
471
+
472
+ Status: started with `rangeRect()`, a non-mutating geometry fallback for
473
+ collapsed native ranges. Browser policies live in dedicated, independently
474
+ tested files rather than one global fixes bundle; semantic editing behavior
475
+ stays in its owning command, input, normalization, or UI responsibility.
476
+
477
+ - Isolate feature-detected Chromium, Firefox, and WebKit differences by the
478
+ primitive or input behavior they correct.
479
+ - Promote every discovered browser quirk into a minimal regression test before
480
+ adding a policy.
481
+ - Run generated range/DOM cases and interaction suites repeatedly in all target
482
+ engines.
483
+ - Verify teardown, memory-sensitive listener ownership, accessibility, IME,
484
+ mobile selection, touch, clipboard, drag/drop, and Shadow DOM.
485
+
486
+ The first release requires all suites green in the latest stable target
487
+ browsers, complete responsibility READMEs/TODOs, and no `document.execCommand()`
488
+ usage anywhere in RTE.