feat(svg-editor): @grida/text-editor + @grida/hud refactor + @grida/svg-editor#714
feat(svg-editor): @grida/text-editor + @grida/hud refactor + @grida/svg-editor#714softmarshmallow wants to merge 7 commits into
Conversation
Backend-agnostic text editor engine packaged as @grida/text-editor, with a react / react-dom-style split: - core (`@grida/text-editor`) is platform-agnostic — no DOM globals, no navigator, no setInterval polyfill. Exports TextEditor + the BYOB contracts (LayoutEngine, Surface, InputProvider, Clipboard). - `@grida/text-editor/dom` ships DomInputRelay, DomClipboard, and a `createTextEditor` convenience that wires the caret-blink loop. Adds a shared JSON fixture corpus at fixtures/text-editor/ that the Rust crate (crates/grida/src/text_edit/) and the TS package both consume — divergence between the two engines fails CI in both languages. Includes the harness on both sides and registers the new shared_fixtures_tests module in the crate. Also patches the WG manifesto with the "selection collapse on navigation" rule, previously observed-but-undocumented behavior. Status: experimental. The shape of the API, command vocabulary, and scope are unsettled. First consumer (@grida/svg-editor) lands in a follow-up commit on this branch.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| if (this.entries.length > MAX_ENTRIES) { | ||
| const drop = this.entries.length - MAX_ENTRIES; | ||
| this.entries.splice(0, drop); | ||
| this.cursor = Math.max(0, this.cursor - drop); |
…narios Add `selection-intent.md` as the implementation-agnostic router spec (per-overlay routing rules, scene-pick fallback, scenario catalog, discriminator state diagram, conformance contract). Sibling to the existing UX-narrative `selection.md`. Update `selection.md` with a top-of-doc cross-link to the formal spec and tag each worked test case with its scenario name (e.g. `BodyNarrowOrDrag`) so the scenario names act as a grep-discoverable lingua franca between the two docs.
Move the declarative keybinding primitives (KeyCode enum, M modifier bitmask, Keybinding types, kb/c/seq/platformKb builders, platform resolution, UI labels) out of editor/grida-canvas/ into a new shared workspace package @grida/keybinding. The package is intentionally passive — it defines, resolves, and matches; it is not a hotkey manager. Consumers assemble their own commands/keymap/dispatch on top. The react-hotkeys-hook bridge (keyCodeToHotkeyStr, keybindingToHotkeysString) stays editor-local in editor/grida-canvas/keybinding-hotkeys.ts since it's specific to the main editor's chosen hotkey runtime. Adds match(event, binding), eventToChunk(event), chunkKey(chunk) — the only new primitive — for hosts that own their own keydown listener and want a passive "does this DOM event satisfy this binding?" check. KeyCode and M change from const enum to plain enum so the values survive package boundaries under isolatedModules. All 8 main-editor consumers redirected to import from @grida/keybinding. Editor build + typecheck pass; 33 new unit tests cover resolution, matching, and builder shapes.
The lockfile from the previous commit was regenerated locally while `packages/grida-canvas-hud/package.json` had an uncommitted vitest devDependency on disk, so the lockfile picked up `vitest@^4` for that package. The package.json change was not part of the keybinding scope and remained unstaged, leaving the committed lockfile inconsistent with HEAD's hud/package.json and breaking `pnpm install --frozen-lockfile` on CI. Regenerated cleanly. `--frozen-lockfile` install now passes against HEAD.
Replaces the flat top-level files (hud.ts, lasso.ts, marquee.ts,
snap-guide.ts, measurement-guide.ts) with a one-directional layered
architecture:
primitives/ — dumb HUDDraw shapes (HUDCanvas, builders); no state
event/ — pure state machine (gesture, hit-regions, click-tracker,
handles, intent, transform); no DOM, no canvas
surface/ — Surface class wires the host providers + draw loop
Surface options renamed to match the new vocabulary:
hitTester → pick, boundsOf → shapeOf (returns SelectionShape, not Rect).
Adds a vitest suite under __tests__/ (68 tests across decision, state,
click-tracker, handles, hit-regions, transform, measurement-guide). The
event layer is DOM-free, so tests run under the node environment.
Summary
Three packages, landed on one branch because they're mutually motivating — the SVG editor is the first consumer of both the new text editor and the restructured HUD.
@grida/text-editor— backend-agnostic text editor engine, with areact/react-dom-style split (core platform-agnostic; browser wiring in/domsubpath). Adds a shared JSON fixture corpus atfixtures/text-editor/consumed by both the Rust crate (crates/grida/src/text_edit/) and the TS package — drift between the two engines now fails CI in both languages.@grida/hud— restructured from flat top-level files into a layered architecture (primitives/←event/←surface/). Surface options renamedhitTester → pick,boundsOf → shapeOf. Adds 68 vitest tests covering the pure event layer.@grida/svg-editor— SVG-canvas editor that wiresLayoutEngine+SurfaceviaSvgTextSurfaceand consumes both new packages. Lands in a follow-up commit on this branch.Status: draft / experimental. API shape, command vocabulary, and scope are unsettled. Do not depend on any of these packages from production code.
Commits in this PR
feat(text-editor): add @grida/text-editor + shared fixtures— the text editor package, the cross-language fixture harness, and the WG manifesto's "selection collapse on navigation" rule.feat(keybinding): extract @grida/keybinding shared package— extracted shared keybinding utilities used by both editors.refactor(hud): split @grida/hud into event/primitives/surface layers— restructure the HUD into the layered shape the new svg-editor depends on. RenameshitTester → pick,boundsOf → shapeOf. Adds vitest suite (68 tests).feat(svg-editor): add @grida/svg-editor— the SVG host that wires everything together. Includes the dev page at/dev/svg-editor.What's in the text-editor package
@grida/text-editor):TextEditororchestrator + pure layers (session,edit-command,history,boundaries,keymap) + BYOB contracts (LayoutEngine,Surface,InputProvider,Clipboard). No DOM globals, nonavigator, nosetIntervalpolyfill —tickBlink()is called by the host.@grida/text-editor/dom):DomInputRelay(hidden textarea + IME),DomClipboard, and acreateTextEditorconvenience that wires the caret-blink loop on a 530 mssetInterval.fixtures/text-editor/v1.jsonand are mirrored exactly on the Rust side.The contract is documented in
packages/grida-text-editor/README.md; the manifesto for both implementations lives atdocs/wg/feat-text-editing/.What's in the HUD restructure
Replaces the flat top-level files (
hud.ts,lasso.ts,marquee.ts,snap-guide.ts,measurement-guide.ts) with a one-directional layered architecture:primitives/— dumb HUDDraw shapes (HUDCanvas, builders); no state.event/— pure state machine (gesture, hit-regions, click-tracker, handles, intent, transform); no DOM, no canvas.surface/—Surfaceclass wires the host providers + draw loop.Public API renamed:
Surfaceconstructor takespick: (point) => NodeId | nullandshapeOf: (id) => SelectionShape | null(in place ofhitTester/boundsOf).shapeOfreturns aSelectionShapediscriminated union (rect / line / unresolved) so vector-line selections can render the right chrome.The 68 new vitest tests run under the node environment (the event layer is DOM-free, which is the whole point of the split). Decision-table coverage (37 scenarios) pins the pointer-down classifier behaviour.
The contract is documented in
packages/grida-canvas-hud/README.md.Test plan
pnpm turbo test --filter @grida/text-editor— 129/129 pass (~37 ms)pnpm turbo test --filter @grida/hud— 68/68 pass (~24 ms)cargo test -p grida text_edit::shared_fixtures— Rust harness consumes the samev1.json, 24/24 passpnpm turbo typecheck build --filter @grida/text-editor --filter @grida/hud— cleanpnpm lint— 0 errors@grida/svg-editorsmoke check on/dev/svg-editorafter follow-up commit