compose: WYSIWYG spike — visual paragraph editing + markup op contract - #302
Open
BeauBoorman wants to merge 2 commits into
Open
compose: WYSIWYG spike — visual paragraph editing + markup op contract#302BeauBoorman wants to merge 2 commits into
BeauBoorman wants to merge 2 commits into
Conversation
Visual editing for plain single-line paragraphs over Oliver's own render, with the contract every further node type reuses (docs/WYSIWYG-DESIGN.md): - ComposeBlockMap: conservative line-shape classification (the highlighter's sniffing posture, never a parse mapping source blocks to rendered blocks; alignment with the DOM is verified, not assumed — mismatch disables visual mode instead of guessing offsets. - ComposeMarkupOp: the closed op set (insert/replace/delete as pure buffer splices, surrogate-pair-aware collapsed deletes); unmappable DOM edits are never guessed, the next reconcile snaps the DOM back to the buffer. - ComposeVisualDocument: drawmeanelephant#230 document assembly + the bridge script (data-block numbering, plaintext-only editable blocks, beforeinput forwarding, Enter interception, caret/scroll restore). - ComposeVisualEditorView: WKWebView host under the drawmeanelephant#230 sandbox; edits splice ComposeDocument.text (the single source of truth); reconcile re-renders through Oliver after edit silence. - ComposeEditorView: Visual toolbar toggle (Markdown only). - GoToLineSheet extracted to its own file (ComposeEditorView was over the lint file budget). The DOM is ephemeral paint like the highlighter's attributes; Oliver stays the only renderer; no parallel document model. Tests: 4 new suites (block map classification/alignment, op derivation + application, document assembly + block-child counter, and a real-WKWebView E2E round trip proving a synthetic beforeinput lands in the buffer and marks it dirty). Suite: 580 tests, 0 failures. SwiftLint --strict: 0. EOF )
…osition, cluster-delete fixes Addresses the independent gate FAIL (dme-fjt, cw2 verdict org 4defea8): - ComposeMarkupOpTests.swift was committed empty at 8a71084 (the "4 new suites" claim was 3 real + 1 empty — retracted then). This restores the full op-contract coverage: 29 tests across insert/replace/delete derivation (incl. offset clamping), collapsed surrogate-pair and ZWJ family deletes, never-guess nil paths (unmapped input types, nil data, non-editable blocks, out-of-range blocks), apply() splice + caret math, and the derive-then-apply convenience round trip. - Two source bugs the new suite exposed, both fixed: - insertText over a selection double-translated the buffer offset (rendered offset passed back through bufferOffset, so splices landed one block-anchor too far right). - Collapsed backward delete did not walk continuation units leftward (ZWJ families split); backwardClusterExtent now mirrors the forward isContinuationScalar walk. - Paste/composition are now deliberately unmapped, host AND DOM side: insertFromPaste payload rides dataTransfer (never data), IME composition commits half-composed text — both return nil (buffer untouched) and the bridge preventDefaults the same set so paint never desyncs awaiting reconcile. insertText with nil data returns nil (never guess an empty splice over a selection). Documented in WYSIWYG-DESIGN.md derivation section as the spike limitation, paste as a follow-up card. - ComposeVisualDocumentTests gains the unmapped-inputType guard test. Fresh self-check: ContractTests 610 executed / 0 failures / 5 skipped (pre-existing); suite presence verified per-file (non-zero blobs) AND per xcresult (29 MarkupOp test entries, all Passed); swiftlint strict 0; swiftformat 0; Solipsist app build SUCCEEDED (env-overridden embed).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the WYSIWYG spike plan: survey → one node type → op contract.
Scope (deliberately small): visual editing for plain single-line
paragraphs over Oliver's own render, plus the contract every further node
type reuses. Not a full WYSIWYG editor — headings/lists/markers/paste are
follow-up cards in
docs/WYSIWYG-DESIGN.md.ComposeBlockMap— conservative line-shape classification (thehighlighter's sniffing posture, never a parse) mapping source blocks to
rendered blocks; alignment with the DOM is verified, not assumed —
mismatch disables visual mode instead of guessing offsets.
ComposeMarkupOp— the closed op set (insert/replace/delete as purebuffer splices, surrogate-pair and ZWJ-family-aware collapsed deletes);
unmappable DOM edits are never guessed (paste/IME-composition payload
rides
dataTransfer/half-composed runs — nil host-side +preventDefaultDOM-side), the next reconcile snaps the DOM back to thebuffer.
ComposeVisualDocument— Compose Preview: Replace NSAttributedString with WKWebView #230 document assembly + the bridge script(data-block numbering, plaintext-only editable blocks, beforeinput
forwarding, Enter + non-op-set interception, caret/scroll restore).
ComposeVisualEditorView— WKWebView host under the Compose Preview: Replace NSAttributedString with WKWebView #230 sandbox; editssplice
ComposeDocument.text(the single source of truth); reconcilere-renders through Oliver after edit silence.
ComposeEditorView— Visual toolbar toggle (Markdown buffers only).GoToLineSheetextracted to its own file (ComposeEditorView was overthe lint file budget).
The DOM is ephemeral paint like the highlighter's attributes; Oliver stays
the only renderer; no parallel document model. Marker verbs (Cmd-B etc.)
are designed-in as existing
ComposeFormattransforms at mapped ranges —second node-type card, no new contract.
Tests: 4 new suites — block map classification/alignment (18), op
derivation + application incl. surrogate/ZWJ collapsed deletes and
never-guess nil paths (29), document assembly + bridge guards (11), and a
real-WKWebView E2E round trip: synthetic beforeinput → buffer mutated +
dirty. Full ContractTests run: 610 executed / 0 failures / 5 pre-existing
skips. swiftlint --strict 0; swiftformat 0.
Notes for review:
(recent upstream merges are unsigned); happy to re-sign if preferred.
fixed a selection-splice offset bug and a ZWJ backward-delete split):
org/ops/reviews/dme-2026-09-06/adversarial/solipsist-wysiwyg-gate-cw2*.md.