docs/plans/2026-05-26-slate-v2-comment-mode-text-selection.md
Objective:
Fix the Slate v2 examples/comment-mode regression where users cannot select
text inside the comment-mode editor.
Goal plan: docs/plans/2026-05-26-slate-v2-comment-mode-text-selection.md
Template: docs/plans/templates/task.md
Primary template: docs/plans/templates/task.md
Applied packs:
Task source:
http://localhost:3100/examples/comment-mode.Completion threshold:
.tmp/slate-v2; larger
limitations are listed with exact repro and owner.slate-react package tests, typecheck, lint, and
.tmp/slate-v2 fast bun check pass.node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-comment-mode-text-selection.md passes.Verification surface:
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/src/editable/runtime-before-input-events.ts.tmp/slate-v2/packages/slate-react/src/editable/runtime-input-events.ts.tmp/slate-v2/packages/slate-react/src/editable/input-router.ts.tmp/slate-v2/packages/slate-react/src/editable/model-input-strategy.ts.tmp/slate-v2/playwright/integration/examples/comment-mode.test.ts.tmp/slate-v2/playwright/integration/examples/read-only.test.ts.tmp/slate-v2/packages/slate-react/test/**http://localhost:3100/examples/comment-mode.Constraints:
Boundaries:
.tmp/slate-v2/packages/slate-react/**,
.tmp/slate-v2/playwright/integration/examples/comment-mode.test.ts,
.tmp/slate-v2/playwright/integration/examples/read-only.test.ts,
.tmp/slate-v2/.changeset/**, and this plan.http://localhost:3100/examples/comment-mode.Blocked condition:
Task state:
Current verdict:
Completion rule:
update_goal(status: complete) while any required checklist item
remains unchecked. If an item does not apply, check it and add N/A: <reason>.update_goal(status: complete) until every completion threshold
above is satisfied, final handoff evidence is recorded, and
node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-comment-mode-text-selection.md passes.Start Gates:
| Gate | Applies | Evidence |
|---|---|---|
| Skill analysis before edits | yes | task, debug, testing, autogoal, changeset, and autoreview loaded where they owned gates. |
| Active goal checked or created | yes | Active goal created for the comment-mode selection regression. |
| Source of truth read before edits | yes | User prompt plus site/examples/ts/comment-mode.tsx, existing comment-mode Playwright test, and read-only/runtime input files read. |
| Tracker comments and attachments read | no | N/A: no tracker. |
| Video transcript evidence required | no | N/A: no video. |
docs/solutions checked for non-trivial existing-code work | no | N/A: live local regression with direct repro and no matching solution needed. |
| TDD decision before behavior change or bug fix | yes | Added browser regression rows for pointer selection and updated read-only behavior tests. |
| Branch decision for code-changing task | yes | No PR/commit requested; current checkout used as-is. |
| Release artifact decision | yes | Added .changeset/slate-react-readonly-selection.md for slate-react user-visible behavior change. |
| Browser tool decision for browser surface | yes | Used Playwright against http://localhost:3100/examples/comment-mode; browser-use tool was unavailable in this tool context. |
| PR expectation decision | yes | N/A: no PR requested. |
| Tracker sync expectation decision | yes | N/A: no tracker. |
| Browser pack selected | yes | Browser pack selected by goal scratchpad. |
| Browser route / app surface identified | yes | http://localhost:3100/examples/comment-mode. |
| Browser tool decision recorded | yes | Playwright route tests and one-off repro probes are the browser proof. |
| Console/network caveat policy recorded | yes | Playwright route tests would fail on runtime page errors; no network-dependent surface. |
Work Checklist:
<video-transcripts> XML, or marked N/A with reason..agents/**, .claude/**,
.codex/**, skills, hooks, commands, prompts, or user-action tooling.Completion Gates:
| Gate | Applies | Required action | Evidence |
|---|---|---|---|
| Named verification threshold | yes | Done: run focused route, package, typecheck, lint, and fast repo checks. | Commands listed in Verification evidence passed. |
| Bug reproduced before fix | yes | Done: record failing browser repro. | Before fix, pointer drag in #comment-mode produced selectedText: "", `selection:0.0:0 |
| Targeted behavior verification | yes | Done: run focused browser tests. | Comment-mode/read-only Playwright matrix passed: 9 passed, 3 intentional exact-range skips. |
| TypeScript or typed config changed | yes | Done: run relevant typecheck. | .tmp/slate-v2: bun --filter slate-react typecheck passed; bun check typecheck stages passed. |
| Package exports or file layout changed | no | N/A: no exports or file layout changes. | N/A. |
| Package manifests, lockfile, or install graph changed | no | N/A: no manifest or lockfile change. | N/A. |
| Agent rules or skills changed | no | N/A: no agent/tooling source changed. | N/A. |
| Workspace authority proof | yes | Done: run checks in .tmp/slate-v2, the owning sibling repo. | All package/browser proof commands ran in /Users/zbeyens/git/plate-2/.tmp/slate-v2; plan checker ran in root because plan lives there. |
| Browser surface changed | yes | Done: run browser route tests. | Comment-mode pointer selection passes across Chromium, Firefox, mobile, and WebKit. |
| Browser final proof | yes | Done: exact browser verification caveat recorded. | Automated Playwright proof only; no screenshot artifact. |
| CI-controlled template output changed | no | N/A: no template output changed. | N/A. |
| Package behavior or public API changed | yes | Done: add changeset. | .tmp/slate-v2/.changeset/slate-react-readonly-selection.md. |
| Registry-only component work changed | no | N/A: not registry-only work. | N/A. |
| Docs or content changed | yes | Done: plan only. | This plan records evidence; no public docs changed. |
| High-risk mini gate | yes | Done: record failure mode and boundary. | Risk: contentEditable read-only could leak mutations; proof covers read-only keyboard typing and native input repair path. |
| Agent-native review for agent/tooling changes | no | N/A: no agent/tooling changes. | N/A. |
| Local install corruption suspected | no | N/A: failures matched real test contracts and were fixed. | N/A: reinstall not run. |
| Autoreview for non-trivial implementation changes | yes | Attempted and blocked by review helper timeout. | /opt/homebrew/bin/timeout 180 ... autoreview --mode local --no-web-search ... exited 124 with no output. |
| PR create or update | no | N/A: no PR requested. | N/A. |
| PR proof image hosting | no | N/A: no PR body. | N/A. |
| Tracker sync-back | no | N/A: no tracker. | N/A. |
| Final handoff contract | yes | Done: fields below filled. | Final response summarizes fix, proof, and autoreview blocker. |
| Final lint | yes | Done: run formatter and lint. | .tmp/slate-v2: bun lint:fix and bun lint passed. |
| Goal plan complete | yes | Done. | node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-comment-mode-text-selection.md passed. |
| Browser interaction proof | yes | Done: exercise route interactions. | Playwright pointer selection tests passed. |
| Browser console/network check | yes | Done by test failure policy. | Local route tests are not network-dependent. |
| Browser final proof artifact | yes | Done: exact caveat recorded. | Automated Playwright proof only; no screenshot artifact. |
Phase / pass table:
| Phase | Status | Evidence | Next |
|---|---|---|---|
| Intake and source read | complete | Source prompt, example, existing tests, runtime input/selection ownership files read. | implementation complete |
| Implementation | complete | Read-only editable remains contentEditable with aria-readonly; beforeinput/input paths block read-only mutation. | verification complete |
| Verification | complete | Browser matrix, package tests, typecheck, lint, and bun check passed. | closeout complete |
| PR / tracker sync | complete | N/A: no PR, commit, push, or tracker requested. | final response |
| Closeout | complete | Plan evidence recorded; checker run before final. | final response |
Findings:
comment-mode coverage cheated with a programmatic DOM range; it did
not prove real pointer selection.#comment-mode selected no native text and
left Slate selection collapsed at 0.0:0|0.0:0.<Editable> rendered contentEditable={false}. That
blocks the native editing surface needed for browser text selection in this
route.contentEditable=true,
the native input repair path must not treat read-only DOM input as real model
input.Decisions and tradeoffs:
slate-react read-only semantics, not the comment-mode example. Comment
mode should consume a selectable read-only editor.contentEditable=true,
role="textbox", and aria-readonly="true".Implementation notes:
EditableDOMRoot always renders a native editable surface and marks read-only
mode with aria-readonly.runtime-before-input-events.ts prevents read-only beforeinput from mutating
the DOM.input-router.ts and runtime-input-events.ts make native DOM input respect
read-only instead of invoking DOM repair as model input.model-input-strategy.ts returns a force-render repair only when read-only
DOM text has diverged from model text.comment-mode.test.ts adds a real pointer selection row.read-only.test.ts now asserts the new contract: focus/select allowed,
typing blocked.Review fixes:
contenteditable=false expectations and
a too-broad read-only force-render repair; both were fixed.Error attempts:
| Error / failed attempt | Count | Next different move | Resolution |
|---|---|---|---|
| Plain Node Playwright import failed | 1 | Use repo Bun/Playwright path | Repro succeeded with bun and @playwright/test. |
Firefox read-only test mutated DOM via keyboard.insertText without DOM events | 1 | Use real key events and guard native input path | Test uses keyboard.type; native input path now respects read-only. |
| Autoreview helper silent timeout | 1 | Record review blocker and rely on direct checks | Bounded 180s run exited 124 with no output. |
Verification evidence:
.tmp/slate-v2: one-off repro before fix returned selectedText: "",
selectionLabel: "selection:0.0:0|0.0:0", active element
comment-mode-document..tmp/slate-v2: one-off proof after fix returned selected text
Comment mode in Slate v, selection:0.0:0|0.0:23,
contentEditable: "true", and ariaReadonly: "true"..tmp/slate-v2: one-off typing proof after fix showed comment-mode text did
not include XXX and read-only writes stayed 0..tmp/slate-v2: PLAYWRIGHT_BASE_URL=http://localhost:3100 PLAYWRIGHT_RETRIES=0 bunx playwright test playwright/integration/examples/read-only.test.ts playwright/integration/examples/comment-mode.test.ts passed: 9 passed, 3 skipped..tmp/slate-v2: bun --filter slate-react test:vitest passed: 43 files,
406 tests..tmp/slate-v2: bun --filter slate-react typecheck passed..tmp/slate-v2: bun lint:fix passed..tmp/slate-v2: bun lint passed..tmp/slate-v2: bun check passed./Users/zbeyens/git/plate-2: node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-comment-mode-text-selection.md passed.console.log, test.only, .only(, or
debugger; only the pre-existing android debug comment matched.Final handoff contract:
slate-react read-only editable/input semantics.Final handoff / sync:
Timeline:
bun check
verification passed.Reboot status:
| Question | Answer |
|---|---|
| Where am I? | Closeout complete; final response next. |
| Where am I going? | Run the mechanical autogoal checker, mark the active goal complete, and hand off. |
| What is the goal? | Fix comment-mode pointer text selection while preserving read-only document safety. |
| What have I learned? | Read-only must stay selectable but block beforeinput/native input mutation. |
| What have I done? | Fixed slate-react read-only selection/input semantics, added tests, changeset, and verification. |
Open risks: