u2ui 1.5.3 → 1.5.7

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