Skip to content

feat(svg-editor): @grida/text-editor + @grida/hud refactor + @grida/svg-editor#714

Draft
softmarshmallow wants to merge 7 commits into
mainfrom
feature/grida-svg-editor
Draft

feat(svg-editor): @grida/text-editor + @grida/hud refactor + @grida/svg-editor#714
softmarshmallow wants to merge 7 commits into
mainfrom
feature/grida-svg-editor

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented May 13, 2026

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 a react / react-dom-style split (core platform-agnostic; browser wiring in /dom subpath). Adds a shared JSON fixture corpus at fixtures/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 renamed hitTester → pick, boundsOf → shapeOf. Adds 68 vitest tests covering the pure event layer.
  • @grida/svg-editor — SVG-canvas editor that wires LayoutEngine + Surface via SvgTextSurface and 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

  1. 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.
  2. feat(keybinding): extract @grida/keybinding shared package — extracted shared keybinding utilities used by both editors.
  3. refactor(hud): split @grida/hud into event/primitives/surface layers — restructure the HUD into the layered shape the new svg-editor depends on. Renames hitTester → pick, boundsOf → shapeOf. Adds vitest suite (68 tests).
  4. (coming next) 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

  • Core (@grida/text-editor): TextEditor orchestrator + pure layers (session, edit-command, history, boundaries, keymap) + BYOB contracts (LayoutEngine, Surface, InputProvider, Clipboard). No DOM globals, no navigator, no setInterval polyfill — tickBlink() is called by the host.
  • DOM subpath (@grida/text-editor/dom): DomInputRelay (hidden textarea + IME), DomClipboard, and a createTextEditor convenience that wires the caret-blink loop on a 530 ms setInterval.
  • Tests: 129 unit tests against the pure layers (no DOM); 24 of them load fixtures/text-editor/v1.json and are mirrored exactly on the Rust side.

The contract is documented in packages/grida-text-editor/README.md; the manifesto for both implementations lives at docs/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/Surface class wires the host providers + draw loop.

Public API renamed: Surface constructor takes pick: (point) => NodeId | null and shapeOf: (id) => SelectionShape | null (in place of hitTester / boundsOf). shapeOf returns a SelectionShape discriminated 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 same v1.json, 24/24 pass
  • pnpm turbo typecheck build --filter @grida/text-editor --filter @grida/hud — clean
  • pnpm lint — 0 errors
  • @grida/svg-editor smoke check on /dev/svg-editor after follow-up commit

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.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment May 14, 2026 6:13am
grida Ready Ready Preview, Comment May 14, 2026 6:13am
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
code Ignored Ignored May 14, 2026 6:13am
legacy Ignored Ignored May 14, 2026 6:13am
backgrounds Skipped Skipped May 14, 2026 6:13am
blog Skipped Skipped May 14, 2026 6:13am
viewer Skipped Skipped May 14, 2026 6:13am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83d2fd0f-a9f3-41b8-be1d-9f24cc2cadb8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/grida-svg-editor

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.
@vercel vercel Bot temporarily deployed to Preview – blog May 13, 2026 18:34 Inactive
@vercel vercel Bot temporarily deployed to Preview – backgrounds May 13, 2026 18:34 Inactive
@vercel vercel Bot temporarily deployed to Preview – viewer May 13, 2026 18:34 Inactive
@vercel vercel Bot temporarily deployed to Preview – grida May 13, 2026 18:34 Inactive
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.
@vercel vercel Bot temporarily deployed to Preview – blog May 14, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – viewer May 14, 2026 06:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – backgrounds May 14, 2026 06:10 Inactive
@softmarshmallow softmarshmallow changed the title feat(text-editor): add @grida/text-editor + @grida/svg-editor feat(svg-editor): @grida/text-editor + @grida/hud refactor + @grida/svg-editor May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant