docs/plans/2026-05-26-slate-v2-arrow-right-paragraph-edge.md
Objective:
Fix the Slate v2 hidden-content regression where ArrowRight from the end of the
first paragraph did not advance. Keep the default safe policy: ArrowRight at a
visible tab edge must not activate hidden tab content unless the example
explicitly selects selectionPolicy="materialize".
Goal plan: docs/plans/2026-05-26-slate-v2-arrow-right-paragraph-edge.md
Template: docs/plans/templates/task.md
Applied packs:
Task source:
/examples/hidden-content-blocksCompletion threshold:
.tmp/slate-v2.@Browser proves the actual route interaction.Verification surface:
.tmp/slate-v2/packages/slate-react: focused Vitest contract for keyboard
boundary movement..tmp/slate-v2: hidden-content Playwright route test..tmp/slate-v2: Slate React, Slate DOM, and site typechecks.@Browser: hidden-content route interaction proof..tmp/slate-v2: scoped format/lint plus autoreview.plate-2: autogoal plan checker.Constraints:
materialize is explicitly selected.Boundaries:
.tmp/slate-v2 package/runtime code, route test, and
matching changeset; this plan file records evidence.@Browser on the hidden-content example route.Blocked condition:
Task state:
Current verdict:
@Browser proof, release artifact, and slice
review are completeStart Gates:
| Gate | Applies | Evidence |
|---|---|---|
| Skill analysis before edits | yes | task, debug, autogoal, Browser:browser, and autoreview used |
| Active goal checked or created | yes | active goal created for this file |
| Source of truth read before edits | yes | user report and live hidden-content files inspected |
| TDD decision before behavior fix | yes | failing focused contract added before the durable fix |
| Browser tool decision | yes | @Browser used for final route proof |
| Package/API pack selected | yes | Slate DOM/React runtime behavior changed |
| Release artifact selected | yes | .tmp/slate-v2/.changeset/slate-react-hidden-tab-navigation.md updated |
| Barrel/export impact decision | yes | no exported file layout changed; no barrel generation needed |
Work Checklist:
Completion Gates:
| Gate | Applies | Required action | Evidence |
|---|---|---|---|
| Named verification threshold | yes | Run focused unit, route, type, browser, review, checker | all recorded below |
| Bug reproduced before fix | yes | Add failing focused harness | keyboard-input-strategy-contract.test.ts first failed p1-end ArrowRight by snapping back |
| Targeted behavior verification | yes | Run focused contract and route tests | contract 14 passed; route 3 passed |
| TypeScript changed | yes | Run package and site typechecks | bun --filter slate-react typecheck, bun --filter slate-dom typecheck, bun typecheck:site passed |
| Package exports or file layout changed | no | N/A | no barrel/export layout changes |
| Package manifests or install graph changed | no | N/A | no manifest or lockfile changes in this slice |
| Workspace authority proof | yes | Run commands in .tmp/slate-v2 | all package/site/playwright commands ran there |
| Browser surface changed | yes | Use @Browser | final Browser proof recorded below |
| Package behavior changed | yes | Update changeset | .changeset/slate-react-hidden-tab-navigation.md includes slate-react and slate-dom patch entries |
| Local install corruption suspected | no | N/A | no React/install corruption signature |
| Autoreview | yes | Run structured review until clean | final slice-diff Claude review exited 0, no accepted/actionable findings |
| PR create/update | no | N/A | no PR requested |
| Tracker sync-back | no | N/A | no tracker requested |
| Final lint | yes | Run scoped equivalent | bunx biome check ... clean; bun eslint ... returned only config-ignore warnings; full bun lint stopped on unrelated projected-collab-substrate-contract.test.ts formatting |
| Browser interaction proof | yes | Exercise p1-end and tab policy in @Browser | proof JSON recorded below |
| Browser console/network check | yes | Check route proof state | no route error in final Browser proof; Browser used network URL because pane could not load localhost directly |
| Goal plan complete | yes | Run autogoal checker | final checker gate next |
Root Cause:
selectionPolicy="boundary" hidden range.Implementation:
slate-dom now owns DOMCoverage.getPointOutsideBoundary, which walks past
same-policy hidden boundary ranges in the movement direction and handles
multiple ranges from the same boundary with boundaryId:rangeIndex visited
keys.slate-react caret movement now collapses to that outside point for normal
movement and preserves the original anchor only for Shift/extend movement.Review fixes:
Verification evidence:
.tmp/slate-v2/packages/slate-react: bun test:vitest -- keyboard-input-strategy-contract.test.ts passed, 14 tests..tmp/slate-v2: PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/hidden-content-blocks.test.ts --project=chromium passed, 3 tests..tmp/slate-v2: bun --filter slate-react typecheck passed..tmp/slate-v2: bun --filter slate-dom typecheck passed..tmp/slate-v2: bun typecheck:site passed..tmp/slate-v2: bunx biome check packages/slate-dom/src/plugin/dom-coverage.ts packages/slate-react/src/editable/caret-engine.ts packages/slate-react/test/keyboard-input-strategy-contract.test.ts playwright/integration/examples/hidden-content-blocks.test.ts passed..tmp/slate-v2: bun eslint packages/slate-dom/src/plugin/dom-coverage.ts packages/slate-react/src/editable/caret-engine.ts packages/slate-react/test/keyboard-input-strategy-contract.test.ts playwright/integration/examples/hidden-content-blocks.test.ts had zero errors; three files are ignored by ESLint config..tmp/slate-v2: full bun lint was attempted and stopped on unrelated formatting in packages/slate-react/test/projected-collab-substrate-contract.test.ts..tmp/slate-v2: final slice-diff autoreview exited 0 with no accepted/actionable findings.Browser proof:
@Browser / Codex in-app Browser.http://100.102.180.93:3100/examples/hidden-content-blocks.Intro visible before hidden blocks., offset 35.Overview tab visible text, offset 0; accordion secret absent; Details tab inactive.boundary, Overview active, Details inactive, Details hidden text absent.materialize, Overview inactive, Details active, Details hidden text present.Error attempts:
| Attempt | Count | Resolution |
|---|---|---|
@Browser to localhost:3100 hit stale empty response | 1 | restarted bun serve, used Browser-accessible network URL |
| Codex autoreview hung silently | 1 | killed exact helper process and used scoped Claude no-tools review |
| First slice review found extend/multi-range defects | 1 | fixed and reran tests/review |
| Second slice review found plain line-move defect | 1 | fixed and reran tests/review |
Final handoff contract:
Phase / pass table:
| Phase | Status | Evidence | Next |
|---|---|---|---|
| Intake and source read | complete | user report, route, caret, DOM coverage, tests read | implementation |
| Implementation | complete | Slate DOM helper, Slate React caret handling, tests, changeset | verification |
| Verification | complete | unit, Playwright, typechecks, Browser, scoped lint | review |
| Review | complete | final slice autoreview clean | closeout |
| PR / tracker sync | complete | N/A, not requested | final response |
| Closeout | complete | plan ready for checker | final response |
Timeline:
@Browser.Reboot status:
| Question | Answer |
|---|---|
| Where am I? | Closeout |
| Where am I going? | Run autogoal checker, mark goal complete, final response |
| What is the goal? | Fix p1-end ArrowRight without weakening hidden-tab default |
| What have I learned? | Snap-back was the wrong boundary behavior; skipped movement needs separate collapsed vs extend handling |
| What have I done? | Implemented package-owned boundary skipping and verified it in tests and Browser |
Open risks: