Back to Plate

Slate v2 editable void mouse unfocus

docs/plans/2026-05-25-slate-v2-editable-void-mouse-unfocus.md

53.0.814.6 KB
Original Source

Slate v2 editable void mouse unfocus

Objective: Fix the .tmp/slate-v2 editable-void child-root mouse unfocus regression: when focus is inside a same-runtime editable void child root, clicking a parent editor paragraph or empty parent block must move focus and selection to the parent root and clear the child-root selection, without regressing child-root click, paste, drop, or keyboard navigation.

Goal plan: docs/plans/2026-05-25-slate-v2-editable-void-mouse-unfocus.md

Task source:

  • type: user bug report
  • id / link: chat prompt on 2026-05-25
  • title: Editable void child root cannot unfocus by mouse click outside it
  • acceptance criteria: reproduce the mouse unfocus failure, add Playwright coverage for outside clicks, fix ownership at the reusable Slate React boundary, verify full editable-voids Chromium route, run relevant package gates, add release note, run autoreview.

Completion threshold:

  • Close only after the new Playwright repro fails before the fix and passes after it.
  • Close only after the full editable-voids.test.ts Chromium route passes against rebuilt slate-react dist.
  • Close only after slate-react resolver unit coverage, package typecheck, site typecheck, lint, changeset, autoreview, and this autogoal completion check are clean.

Verification surface:

  • bun --filter ./packages/slate-react test:vitest -- root-interaction-resolver
  • bun --filter ./packages/slate-react typecheck
  • bun typecheck:site
  • bun --filter ./packages/slate-react build
  • PLAYWRIGHT_RETRIES=0 bun run playwright playwright/integration/examples/editable-voids.test.ts --project=chromium
  • bun lint
  • /Users/zbeyens/git/plate-2/.agents/skills/autoreview/scripts/autoreview --mode local
  • node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-editable-void-mouse-unfocus.md

Constraints:

  • Do not use git branch/status hygiene.
  • Work in .tmp/slate-v2 for code, tests, package build, and browser proof.
  • Keep the fix at the reusable Slate React focus/selection ownership boundary.
  • Do not create a PR, commit, or push.

Boundaries:

  • Source of truth: user bug report plus the existing editable-voids route behavior.
  • Allowed edit scope: .tmp/slate-v2/packages/slate-react, .tmp/slate-v2/playwright/integration/examples/editable-voids.test.ts, .tmp/slate-v2/.changeset, and this goal plan.
  • Browser surface: /examples/editable-voids via repo Playwright Chromium integration.
  • Tracker sync: N/A, no tracker item.
  • Non-goals: broad API redesign, manual browser exploration after Playwright proof, unrelated local diff cleanup.

Blocked condition: Blocked only if the editable-voids route cannot be built or run locally after ruling out a code-caused failure, or if the correct focus ownership boundary requires a user-approved public API change.

Task state:

  • task_type: bug fix
  • task_complexity: normal
  • current_phase: closeout
  • current_phase_status: complete
  • next_phase: final response
  • goal_status: active until completion check and tool close

Current verdict:

  • verdict: fixed
  • confidence: high
  • next owner: user
  • reason: failing mouse-unfocus repro now passes, full route and package gates pass, autoreview reports no accepted/actionable findings.

Start Gates:

GateAppliesEvidence
Skill analysis before editsyesLoaded task, tdd, autogoal, changeset, and autoreview guidance.
Active goal checked or createdyesActive goal created for editable-void child-root mouse unfocus.
Source of truth read before editsyesUser bug report plus editable-voids test/example/source read.
Tracker comments and attachments readnoN/A: chat bug report, no tracker item.
Video transcript evidence requirednoN/A: no video attachment.
docs/solutions checked for existing-code worknoN/A: direct local regression with existing nearby tests and controllers.
TDD decision before behavior changeyesAdded failing Playwright repro before implementation; fixed after red result.
Branch decision for code-changing taskyesN/A: no branch/PR requested; no git hygiene performed.
Release artifact decisionyesAdded .changeset/fix-editable-void-mouse-unfocus.md for slate-react patch.
Browser tool decisionyesUser requested Playwright coverage; repo-owned Chromium integration used.
PR expectation decisionyesN/A: no PR requested.
Tracker sync expectation decisionyesN/A: no tracker item.
Browser pack selectedyesBrowser pack applies because route interaction is the proof surface.
Browser route / app surface identifiedyes/examples/editable-voids, editable-voids.test.ts, Chromium project.
Console/network caveat policy recordedyesPlaywright route proof used; manual console/network sweep is out of scope for this regression.

Work Checklist:

  • Objective includes outcome, completion threshold, verification surface, constraints, boundaries, and blocked condition.
  • Task source classified with source type, title, task type, acceptance criteria, caveats, likely files/routes/packages, browser surface, and root-cause layer.
  • Required video or screen-recording evidence is cached/read as normalized XML, or marked N/A with reason.
  • Nearby repo instructions and implementation patterns read before edits.
  • Implementation fixes the right ownership boundary, or the narrower choice is recorded with reason.
  • Release artifact requirement recorded: changeset, registry changelog, or N/A with reason.
  • Final handoff shape decided: bug fix with tests, package gates, browser proof, changeset, autoreview.
  • Branch handling recorded for code-changing work: N/A, no PR/commit requested.
  • Local-env-rot retry policy recorded: N/A, no surprising install-corruption failure.
  • Workspace authority recorded: verification commands run in .tmp/slate-v2, plan/check in root plate-2.
  • High-risk note recorded for runtime/browser behavior.
  • Review/autoreview target selected from actual diff state.
  • Agent-native review decision recorded: N/A, no agent/tooling files changed.
  • Browser pack: route, interaction path, and expected visible outcome recorded before proof.
  • Browser pack: browser proof uses repo Playwright Chromium because user requested Playwright regression coverage.
  • Browser pack: console and network errors checked or explicitly out of scope.
  • Browser pack: trace or exact verification caveat ready for final handoff.

Completion Gates:

GateAppliesRequired actionEvidence
Named verification thresholdyesRun all named commandsAll listed verification commands passed except this check, which runs after this edit.
Bug reproduced before fixyesRecord failing test/reproNew Playwright test failed before fix with child root retaining focus/selection after parent click.
Targeted behavior verificationyesRun focused testFocused Chromium test unfocuses editable void child root when clicking outside it passed after fix.
TypeScript or typed config changedyesRun relevant typecheckbun --filter ./packages/slate-react typecheck and bun typecheck:site passed.
Package exports or file layout changednoN/ANo exports or file layout changed.
Package manifests, lockfile, or install graph changednoN/ANo manifest or lockfile changed.
Agent rules or skills changednoN/ANo agent rule/skill files changed.
Workspace authority proofyesRun proof in owning workspaceCode/test/browser commands run in /Users/zbeyens/git/plate-2/.tmp/slate-v2.
Browser surface changedyesRun browser route proofFull editable-voids Chromium route passed: 20 tests.
Browser final proofyesRecord artifact/caveatPlaywright pass count and failing trace before fix recorded; no manual screenshot needed.
CI-controlled template output changednoN/ANo template output touched.
Package behavior or public API changedyesAdd changeset.changeset/fix-editable-void-mouse-unfocus.md added for slate-react patch.
Registry-only component work changednoN/ANo registry work.
Docs or content changednoN/AOnly goal ledger changed; no user docs.
High-risk mini gateyesRecord failure mode and proofRisk: parent root could steal child-root clicks; resolver unit added and full route passed.
Agent-native review for agent/tooling changesnoN/ANo agent/tooling changes.
Local install corruption suspectednoN/ANo env-rot signature.
Autoreview for non-trivial implementation changesyesRun autoreview localAutoreview clean: no accepted/actionable findings.
PR create or updatenoN/ANo PR requested.
PR proof image hostingnoN/ANo PR body.
Tracker sync-backnoN/ANo tracker item.
Final handoff contractyesFill final fieldsFields below completed.
Final lintyesRun lintbun lint passed after changeset.
Goal plan completeyesRun completion checkerTo run after this ledger update.
Browser interaction proofyesExercise target route/interactionPLAYWRIGHT_RETRIES=0 bun run playwright playwright/integration/examples/editable-voids.test.ts --project=chromium passed.
Browser console/network checknoN/ARegression proof is route interaction; manual console/network sweep out of scope.
Browser final proof artifactyesRecord proofPlaywright Chromium full-route pass recorded.

Phase / pass table:

PhaseStatusEvidenceNext
Intake and source readcompleteUser report, editable-voids tests/example, root interaction and selection controllers readimplementation
ImplementationcompleteRoot interaction controller attached to Editable roots; resolver ignores nested editors from parent roots; test and changeset addedverification
VerificationcompleteUnit, typecheck, lint, build, focused repro, full Chromium route passedcloseout
PR / tracker syncnot_applicableNo PR/tracker requestedfinal response
CloseoutcompleteAutoreview clean; plan updated for completion checkfinal response

Findings:

  • Parent editable roots had no root-interaction capture recovery, so when a nested child root owned focus, a mouse click in the parent root could leave DOM/model selection in the child root.
  • Attaching the existing root-interaction controller to Editable roots fixes the reusable boundary.
  • Resolver must treat nested editors as interactive descendants when the current target is an editable root, or parent roots would steal child-root clicks.

Decisions and tradeoffs:

  • Chosen boundary: reusable Slate React root interaction, not a site example workaround.
  • Why not quick patch: changing only editable-voids example would not fix other nested-root surfaces.
  • Why not broader change: no public API change needed; existing controller already models the focus recovery behavior.

Implementation notes:

  • EditableDOMRoot composes root-interaction capture handlers with user capture handlers.
  • resolveRootInteractionTarget now ignores nested editor clicks from parent editable roots.
  • Playwright coverage clicks both a visible parent paragraph and an empty parent block after focusing the child root.

Review fixes:

  • Lint found a React Compiler memoization dependency issue; destructured root interaction handlers fixed it.

Error attempts:

Error / failed attemptCountNext different moveResolution
Test used nonexistent outer.selection.clickTextOffset helper1Use scenario clickTextOffset on the outer harnessFixed test shape.
Initial repro clicked only empty trailing block1Use visible paragraph first, then add empty-block variant after fixBoth variants now covered.
Lint rejected memo deps1Destructure controller callbacks before useMemobun lint passed.

Verification evidence:

  • Red repro: focused Playwright test failed before implementation with outer editor inactive and child selection retained.
  • bun --filter ./packages/slate-react test:vitest -- root-interaction-resolver: passed, 1 file, 5 tests.
  • bun --filter ./packages/slate-react typecheck: passed.
  • bun typecheck:site: passed.
  • bun --filter ./packages/slate-react build: passed.
  • Focused Playwright repro: passed after fix.
  • Full editable-voids route: PLAYWRIGHT_RETRIES=0 bun run playwright playwright/integration/examples/editable-voids.test.ts --project=chromium passed, 20 tests.
  • bun lint: passed.
  • Autoreview: /Users/zbeyens/git/plate-2/.agents/skills/autoreview/scripts/autoreview --mode local passed with no accepted/actionable findings.

Open risks:

  • None known after focused repro, full editable-voids route, typecheck, lint, unit, build, changeset, and autoreview.

Final handoff contract:

  • PR line: N/A, no PR requested.
  • Issue / tracker line: N/A, no tracker item.
  • Confidence line: high; the exact reported mouse-unfocus regression is covered and the neighboring editable-voids route is green.
  • Flow table:
    • Reproduced: Playwright Chromium test failed before fix.
    • Verified: focused repro passed, full route passed, package gates passed.
  • Browser check: Playwright Chromium /examples/editable-voids, 20 tests passed.
  • Outcome: fixed in Slate React root interaction ownership.
  • Caveat: manual browser console/network sweep was out of scope because user requested Playwright regression coverage.
  • Design:
    • Chosen boundary: EditableDOMRoot root interaction plus resolver nested-editor guard.
    • Why not quick patch: example-only patch would miss other same-runtime child roots.
    • Why not broader change: no new API or schema concept was required.
  • Verified: unit, typecheck, build, lint, focused browser repro, full browser route, autoreview.

Final handoff / sync:

  • PR: N/A.
  • Issue / tracker: N/A.
  • Browser proof: Playwright Chromium full route passed.
  • Caveats: no manual browser screenshot; Playwright trace artifacts are generated on failure and focused/full route pass is the proof.

Timeline:

  • 2026-05-25T19:07:52.922Z Task goal plan created.
  • 2026-05-25T19:12Z Failing Playwright repro captured.
  • 2026-05-25T19:19Z Full editable-voids Chromium route passed after fix.
  • 2026-05-25T19:21Z Autoreview passed.

Reboot status:

QuestionAnswer
Where am I?Closeout complete; completion checker is the only remaining mechanical gate.
Where am I going?Run completion checker, close the active goal, respond to user.
What is the goal?Fix editable-void child-root mouse unfocus and prove it with Playwright and package gates.