Skip to content

Add Canvas-based document editor package with Korean IME support#58

Merged
hackerwins merged 15 commits into
mainfrom
feat/document-editor
Mar 21, 2026
Merged

Add Canvas-based document editor package with Korean IME support#58
hackerwins merged 15 commits into
mainfrom
feat/document-editor

Conversation

@hackerwins

@hackerwins hackerwins commented Mar 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add new packages/document package (@wafflebase/docs) — a Canvas-based rich-text document editor following the same architecture as packages/sheet
  • Implement paragraph editing: text input/deletion, Enter to split blocks, Backspace to merge blocks
  • Implement inline formatting (bold, italic, underline, font size), block alignment, and undo/redo
  • Implement Canvas word-wrap, cursor positioning, text selection, and coordinate mapping
  • Add software Hangul syllable assembler for Korean IME support on Mobile Safari (which doesn't fire composition events for hidden textareas)
  • Fix Chrome IME double-insertion bug using microtask-based input event skipping after compositionend
  • Add design doc at docs/design/docs.md

Architecture

  • Data model: Document → Block → Inline hierarchy with Doc class for manipulation
  • Store: DocStore interface + MemDocStore (snapshot-based undo/redo)
  • View: Canvas rendering pipeline (Layout → Paint → Input handling) with hidden textarea for keyboard input
  • Hangul assembler: State machine (EMPTY → LEAD → SYLLABLE → SYLLABLE_TAIL) handling compound tails/vowels and tail splitting

Test plan

  • Unit tests for Doc model (insert, delete, split, merge, formatting) — 18 tests
  • Unit tests for MemDocStore (CRUD, undo/redo) — 8 tests
  • Unit tests for HangulAssembler (syllable assembly, compound tails/vowels, splitting) — 18 tests
  • pnpm verify:fast passes
  • Manual testing on Chrome desktop (standard IME)
  • Manual testing on Mobile Safari (jamo-based Korean input)
  • Manual testing on Android Chrome

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Canvas-based rich-text document editor package with toolbar, demo page, public editor API, undo/redo, cursor/selection, and Hangul assembly for Korean input.
  • Documentation

    • Added comprehensive design spec, task notes, and updated docs site references.
  • Tests

    • Added test suites covering document model, store, layout, Hangul assembly, and IME scenarios.
  • Chores

    • Updated build scripts, package manifests, and site build/copy paths; updated nav label to “Documentation”.

hackerwins and others added 3 commits March 21, 2026 09:31
New @wafflebase/document package providing a minimal Canvas-based
rich text editor (like Google Docs) with the same architecture as
the sheet package: model/store/view layers, Vite build, Vitest.

Features:
- Document model: Document → Block[] → Inline[] hierarchy
- Inline formatting: bold, italic, underline, font size/family, color
- Block styles: alignment (left/center/right), line height, margins
- Canvas rendering with DPR-aware scaling and word-wrap layout
- Cursor with blink animation and pixel-accurate positioning
- Text selection with keyboard (Shift+Arrow) and mouse drag
- Undo/redo via snapshot-based MemDocStore
- IME composition support (compositionstart/end events)
- Software Hangul assembler for Mobile Safari (which sends raw jamo
  without composition events to hidden textareas)
- Playwright cross-browser IME test infrastructure (Chromium + WebKit)
- Demo page with toolbar and event logger for debugging

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Use full paths from repo root so the entropy checker can resolve them.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Flagged by knip dead-code detection.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions

github-actions Bot commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 98.1s

Lane Status Duration
sheets:build ✅ pass 13.1s
verify:fast ✅ pass 48.7s
frontend:build ✅ pass 13.9s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.5s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 15.7s

Verification: verify:integration

Result: ✅ PASS

@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new @wafflebase/document package implementing a Canvas-based document editor: model (blocks/inlines), Doc mutation API, snapshot MemDocStore with undo/redo, layout and coordinate mapping, canvas renderer (cursor/selection), text editor with IME/Hangul assembly, demo, tests, and build/config files.

Changes

Cohort / File(s) Summary
Workspace & CI
\.mcp.json, package.json, .github/workflows/publish-ghpage.yml, \.gitignore
Added MCP server config; updated root scripts to target @wafflebase/document/@wafflebase/documentation; adjusted GH Pages copy paths and gitignore for new docs package.
Documentation & Tasks
docs/design/README.md, docs/design/docs.md, docs/tasks/active/20260320-*.md, docs/design/docs-site.md
Added design spec and task/todo/lessons docs describing the Canvas editor architecture and tracked implementation phases; renamed docs package references to documentation.
Package Manifest & Build
packages/document/package.json, packages/document/tsconfig.json, packages/document/vite.build.ts, packages/document/vite.config.ts
New package manifest, TypeScript config, and Vite build/config for @wafflebase/document with scripts, deps, and declaration generation.
Demo / Static
packages/document/index.html, packages/document/demo.ts
Added demo HTML and runtime demo script exposing a Playwright bridge and toolbar wiring for the editor.
Public API Entrypoint
packages/document/src/index.ts
New consolidated exports re-exporting model, store, view, layout, theme, and editor APIs.
Model Types & Logic
packages/document/src/model/types.ts, packages/document/src/model/document.ts
Defined Document/Block/Inline types and utilities; implemented Doc class with text insert/delete, split/merge, style application and offset resolution.
Store Interface & Implementation
packages/document/src/store/store.ts, packages/document/src/store/memory.ts
Added DocStore interface and MemDocStore snapshot-based in-memory store with undo/redo and block operations.
Layout & Coordinate Mapping
packages/document/src/view/layout.ts
Implemented layout computation (measureText, wrapping), and converters positionToPixel/pixelToPosition for hit-testing and cursor mapping.
Rendering & View Components
packages/document/src/view/theme.ts, packages/document/src/view/doc-canvas.ts, packages/document/src/view/cursor.ts, packages/document/src/view/selection.ts
Added theme/font builder, canvas renderer (runs/lines/selection/cursor), cursor blink management, and selection rectangle/text extraction utilities.
IME / Text Input
packages/document/src/view/hangul.ts, packages/document/src/view/text-editor.ts, packages/document/scripts/verify-ime-browser.mjs
Implemented Hangul assembler for jamo composition, a TextEditor wiring hidden textarea to the canvas with composition handling and software Hangul fallback, plus a Playwright IME verification script.
Editor Integration
packages/document/src/view/editor.ts
Added initialize(container, store?) producing EditorAPI, wiring store, Doc, layout, render loop, cursor, selection, TextEditor, and lifecycle methods.
Tests
packages/document/test/*
Added unit tests for Doc model, MemDocStore, and Hangul assembler.
Frontend Link Text
packages/frontend/src/app/home/nav-bar.tsx
Changed visible nav link text from “Docs” to “Documentation” to match package rename.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TextEditor
    participant IME as IME/Composition
    participant Doc
    participant Store as DocStore
    participant Layout as LayoutEngine
    participant Canvas

    User->>TextEditor: Type / Key / Pointer
    TextEditor->>IME: compositionstart / input events
    IME-->>TextEditor: composing data
    TextEditor->>Doc: insertText / deleteText (compose preview)
    Doc->>Store: updateBlock
    TextEditor->>Layout: computeLayout(blocks, ctx, width)
    Layout->>Canvas: DocumentLayout
    Canvas->>Canvas: render(layout, cursor, selection)
    IME->>TextEditor: compositionend
    TextEditor->>Doc: deleteText(prev composed range) / insertText(final)
    Doc->>Store: updateBlock
    TextEditor->>Layout: computeLayout(...)
    Layout->>Canvas: DocumentLayout
    Canvas->>User: Visual update
Loading
sequenceDiagram
    participant HangulAssembler
    participant Input as jamo char
    participant State as CompositionState
    participant Output as HangulResult

    Input->>HangulAssembler: feed(ch)
    HangulAssembler->>State: classify (consonant/vowel/other)
    alt jamo consonant
        State->>State: onConsonant() update lead/tail
    else jamo vowel
        State->>State: onVowel() combine with lead or split tail
    else non-jamo
        State->>State: flush() commit previous
    end
    State->>Output: return {commit?, composing?}
    Output-->>HangulAssembler: caller applies commit/composing
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Hopping pixels, ink and scope,

Blocks and inlines stitch and cope,
Hangul sings as jamo blend,
Undo-side trails that softly mend,
A tiny editor springs to hope.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add Canvas-based document editor package with Korean IME support' accurately and concisely describes the primary change: introducing a new Canvas-based document editor package with IME support, which matches the core objective and extensive file additions for packages/document.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/document-editor

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.

Tip

You can disable poems in the walkthrough.

Disable the reviews.poem setting to disable the poems in the walkthrough.

The CI frozen-lockfile check failed because playwright was added to
packages/document/package.json but the lockfile was not updated in
the same commit.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🧹 Nitpick comments (4)
docs/design/README.md (1)

26-29: Pre-existing duplicate entry for docs-site.md.

Lines 26 and 29 both reference docs-site.md. Consider removing the duplicate in a follow-up cleanup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/README.md` around lines 26 - 29, The docs index contains a
duplicated row for docs-site.md; remove the redundant entry so the table only
references docs-site.md once (update the table rows that list
[docs-site.md](docs-site.md) to eliminate the duplicate at the second
occurrence), ensuring any descriptive text remains correct and spacing/alignment
of the Markdown table is preserved.
packages/document/index.html (1)

23-27: Consider responsive handling for the editor container.

The fixed 800px width won't adapt to smaller viewports. For a demo page this may be acceptable, but if this will be used for mobile testing (mentioned in PR for Mobile Safari), consider adding a responsive fallback.

📝 Responsive suggestion
     `#editor-container` {
-      width: 800px; margin: 24px auto; background: `#fff`;
+      width: min(800px, 100vw - 32px); margin: 24px auto; background: `#fff`;
       border: 1px solid `#ddd`; border-radius: 4px;
       height: calc(100vh - 300px); position: relative;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/document/index.html` around lines 23 - 27, The CSS for
`#editor-container` uses a fixed width (800px) which breaks on small viewports;
update the selector to use responsive sizing—replace the fixed width with a
fluid width (e.g., width as a percentage or calc(100% - some padding) plus
max-width: 800px) and add a simple media query fallback for very small screens
to adjust height or padding if needed; target the `#editor-container` rule and
ensure border, margin and position behavior remain unchanged while swapping the
fixed width for responsive properties.
packages/document/vite.config.ts (1)

1-3: Consider adding a clarifying comment.

This empty config file alongside vite.build.ts may confuse future maintainers. A brief comment explaining its purpose (dev server defaults vs library build) would help.

📝 Suggested clarification
 import { defineConfig } from 'vite';
 
+// Development server config (uses Vite defaults).
+// Library build config is in vite.build.ts.
 export default defineConfig({});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/document/vite.config.ts` around lines 1 - 3, Add a brief clarifying
comment at the top of this file explaining why an empty Vite config is exported
(e.g., to preserve dev-server/default behavior while library-specific build
config lives in vite.build.ts), so future maintainers understand the presence of
export default defineConfig({}) and the relationship to vite.build.ts.
packages/document/test/view/hangul.test.ts (1)

71-73: Test description typo: expected word and title are inconsistent.

Line 71 says "한아" but Line 73 asserts "하나". Please rename the test title/comment to match the expected output for clearer intent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/document/test/view/hangul.test.ts` around lines 71 - 73, The test
title/comment is inconsistent: it says "한아" but the assertion expects "하나";
update the test description to reflect the expected output by renaming the
it(...) title from '"한아" → ㅎㅏㄴㅏ' (or similar) to '"하나" → ㅎㅏㄴㅏ' so the
human-readable description matches the assemble(['ㅎ','ㅏ','ㄴ','ㅏ']) expectation
and intent around the assemble function's behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/docs.md`:
- Around line 37-39: Update the design doc's IME scope to reflect the
implemented behavior: change the "IME composition handling — deferred (basic
`beforeinput` only)" non-goal to state that Korean IME composition (including
the Mobile Safari workaround added in this PR) is supported; mention the
specific implemented coverage (Korean IME on desktop and mobile Safari
workaround) and, if relevant, note any remaining limitations instead of marking
IME handling as deferred. Reference the "IME composition handling" phrase in the
docs so reviewers can verify the update matches the PR changes.
- Around line 10-30: The doc mentions the old package identity `packages/docs`
and `@wafflebase/docs`; update every occurrence to the new package identity
`packages/document` and `@wafflebase/document` (including the Goals section and
the Structure section where package names and import paths are listed) so
references, examples, and any path strings match the implemented package; search
for the symbols `packages/docs`, `@wafflebase/docs`, and any path examples in
this file and replace them with `packages/document` and `@wafflebase/document`
ensuring consistency across the entire document.

In `@docs/tasks/active/20260320-docs-package-todo.md`:
- Line 5: The doc entry currently shows "Status: COMPLETE" while the manual
validation item "manual smoke test" remains unchecked; update the task so status
reflects reality by either (a) completing the unchecked "manual smoke test" item
at the bottom of the file and adding any required verification notes, or (b)
reverting "Status: COMPLETE" back to an in-progress state and adding a TODO
comment indicating the pending "manual smoke test" and owner; locate the
"Status: COMPLETE" header and the unchecked "manual smoke test" checklist item
to make the change.

In `@packages/document/demo.ts`:
- Around line 23-28: The logEvent function uses innerHTML to build a line which
injects the user-influenced detail and is XSS-prone; replace the innerHTML
construction in logEvent with safe DOM creation: create a container div, create
three spans (one for the padded eventSeq number with style, one for the type
with class cls and textContent set to type.padEnd(20), and one for the detail
with textContent set to detail), append them to the container, then append the
container to logEl—ensuring you reference logEvent, eventSeq and logEl when
making the change.
- Around line 71-77: The clipboard copy handler on the 'btn-copy-log' element
uses a .then() chain without error handling; change the click listener callback
to an async function that awaits navigator.clipboard.writeText(logEl.innerText)
inside a try/catch, update the button text to 'Copied!' on success and to an
error state (e.g., 'Copy failed') on catch, and restore the original 'Copy' text
after a timeout in both paths; ensure you reference the same DOM element
(document.getElementById('btn-copy-log') / logEl) and handle the case where the
element or navigator.clipboard may be unavailable.

In `@packages/document/scripts/verify-ime-browser.mjs`:
- Around line 371-430: The script currently treats missing browser executables
as warnings and still prints "[verify:ime] All IME browser tests passed." even
if no scenarios ran; add a sentinel (e.g., ranAnyScenario = false) and set it
true inside each successful runAllScenarios call (for Chromium and WebKit), then
after both engine blocks check the flag and if still false call
printInstallHelp() and exit non‑zero (or throw) so the script fails when both
browsers are missing; reference chromiumBrowser, webkitBrowser, runAllScenarios
and the final success log to locate where to set the flag and where to fail.

In `@packages/document/src/model/document.ts`:
- Around line 61-84: deleteText can hang if pos.offset is at the end of the
block or length exceeds remaining text because resolveOffset may return
available === 0 and the loop doesn't advance; update deleteText to first compute
the block's remaining characters from pos (e.g., sum of inline lengths after pos
offset via getBlock and resolveOffset or by summing block.inlines), clamp the
requested length to that remaining amount and return early if clamped length is
0, then proceed with the existing loop and calls to
resolveOffset/normalizeInlines to perform deletions.

In `@packages/document/src/store/memory.ts`:
- Around line 23-35: getDocument and getBlock currently return live references
allowing external mutation that bypasses pushUndo; change them to return
deep-cloned copies instead (use cloneDocument for getDocument and
cloneBlock/deep clone for getBlock) so callers cannot modify store internals
directly, and ensure initialize/Doc construction uses the cloned document rather
than the original; leave setDocument as-is (it already clones and calls
pushUndo) and keep pushUndo usage unchanged.

In `@packages/document/src/view/editor.ts`:
- Around line 77-90: render() always paints from y=0 and resizes the canvas to
the container height so layout.totalHeight and container.scrollTop are ignored;
fix by making the canvas height match layout.totalHeight (or at least
max(container height, layout.totalHeight)) and pass the current vertical offset
(container.scrollTop) into docCanvas.render instead of 0, and adjust calls that
compute pixel positions (cursor.getPixelPosition and
selection.getSelectionRects) to accept/compensate for container.scrollTop so
hit-testing and rendering align; update recomputeLayout usage if needed to
ensure layout.totalHeight is computed before resizing/rendering.
- Around line 47-56: Edits are mutating a plain Doc instance created from
storeDoc (via new Doc(storeDoc)) so changes bypass the DocStore undo stack;
refactor so all mutations go through DocStore mutation APIs and record undo
snapshots: instantiate or obtain the live Doc via DocStore (use
docStore.getDocument()/docStore.setDocument patterns or add a
docStore.createLiveDoc helper) and ensure TextEditor, applyStyle, and
applyBlockStyle call the store’s mutation methods that internally call
pushUndo() (or explicitly call docStore.pushUndo() before applying changes) so
every edit is recorded; update the code paths referenced (the Doc instantiation
at the top plus the code areas around TextEditor, applyStyle, applyBlockStyle
and the regions noted at 93-101 and 120-148) to use the store-backed mutation
methods instead of mutating the raw Doc object.

In `@packages/document/src/view/layout.ts`:
- Around line 76-85: computeLayout() currently computes a single lineHeight
using getBlockFontSize() which only inspects block.inlines[0], causing later
larger inlines to overlap; change the calculation to base lineHeight on the
maximum font size in scope instead: either update getBlockFontSize() to scan all
inlines in the block (not just block.inlines[0]) or add a helper like
getLineMaxFontSize(line) and compute lineHeight per line as (lineMaxFontSize *
(inline.style.lineHeight ?? block.style.lineHeight ?? 1.5)), then use that
per-line height when setting line.height and updating blockHeight in the loops
inside computeLayout() (and apply the same fix at the other occurrence around
lines 255-260).
- Around line 129-152: layoutBlock currently only wraps at word-level segments
from measureSegments, so oversized segments (seg.width > maxWidth) never break
and overflow; modify the loop that iterates over segments in layoutBlock to
detect when seg.width > maxWidth and perform a character-level fallback: iterate
the characters of the offending inline (use seg.charStart/charEnd and
block.inlines[seg.inlineIndex] to access text), measure sub-segments per
character (or small slices) and emit runs that fill remaining space then wrap,
updating currentRuns, lineWidth, and lines just like the normal path; apply the
same character-level splitting logic to the second wrapping loop referenced (the
block around lines 171-229) so both wrapping places handle oversized tokens.
- Around line 369-394: The hit-test offset is wrong because run.charStart is
stored per-inline (via measureSegments) so treating it as a line-global index
miscomputes offsets for later runs; fix the calculation in the loop over
targetLine.runs (the block referencing run.charStart and targetLine.runs[0]) by
computing the offset within the line as the sum of the lengths of all previous
runs plus bestOffset (e.g. sum targetLine.runs[0..runIndex-1].text.length) and
then return blockId with offset = charsBeforeLine + that sum + bestOffset
instead of using run.charStart.

In `@packages/document/src/view/selection.ts`:
- Around line 96-123: The multi-line selection branch currently builds rects
using the full line block bounds (lb.x and lb.width), causing
centered/right-aligned lines to highlight into margins; update the three rect
constructions to use the actual run/line bounds instead: for the first-line rect
use startPixel.x and startPixel.width (or the computed run width) instead of
lb.width, for the middle-line loop use each line's actual run x and width rather
than lb.x/lb.width, and for the last-line rect use endPixel.x - runX (or
endPixel.width) anchored at the run's x; locate and change the code that pushes
into rects in the multi-line branch (references: rects array, startPixel,
endPixel, lb, startPixel.height) so each rect is constrained to the line run
bounds rather than the full block.

In `@packages/document/src/view/text-editor.ts`:
- Around line 59-67: The key handler in TextEditor is preventing default for
Cmd/Ctrl+Z but not performing undo/redo; update the branch in the TextEditor
keydown handler (the code path inside the TextEditor class that checks for
Meta/Ctrl+Z and Shift/Meta+Z or Ctrl+Y) to call the document undo/redo APIs and
then re-render: invoke this.doc.undo() for plain Meta/Ctrl+Z and invoke
this.doc.redo() for Meta/Ctrl+Shift+Z and Ctrl+Y (or, if your Doc API uses
different names, call the appropriate undo()/redo() methods), keep
event.preventDefault(), and call this.requestRender() after the change so the
editor updates.
- Around line 351-382: The handler computes newPos from this.cursor.position
even when a selection exists, causing arrows without shift to move from the
wrong spot; change handleArrow so when shiftKey is false and
this.selection.range exists you first collapse the position to the appropriate
selection boundary (for 'left' and 'up' use the earlier/minimum of range.anchor
and range.focus; for 'right' and 'down' use the later/maximum), then call
moveLeft/moveRight/moveVertical using that collapsed base position, then clear
the selection via this.selection.setRange(null) and move the cursor to newPos
and requestRender; reference handleArrow, this.selection.range, range.anchor,
range.focus, moveLeft, moveRight, moveVertical, selection.setRange, and
cursor.moveTo.
- Around line 539-576: The software-assembler path in applyHangulResult doesn't
clear the active selection before inserting the first composing jamo, so
previews get appended instead of replacing selections; update applyHangulResult
so that in the composing branch where (result.composing &&
this.hangulComposingLength === 0 && !result.commit) you call deleteSelection()
before setting this.hangulStartPos (and then proceed to insert result.composing
and set hangulComposingLength), ensuring behavior matches the browser-IME path
that clears selection in handleCompositionStart().

---

Nitpick comments:
In `@docs/design/README.md`:
- Around line 26-29: The docs index contains a duplicated row for docs-site.md;
remove the redundant entry so the table only references docs-site.md once
(update the table rows that list [docs-site.md](docs-site.md) to eliminate the
duplicate at the second occurrence), ensuring any descriptive text remains
correct and spacing/alignment of the Markdown table is preserved.

In `@packages/document/index.html`:
- Around line 23-27: The CSS for `#editor-container` uses a fixed width (800px)
which breaks on small viewports; update the selector to use responsive
sizing—replace the fixed width with a fluid width (e.g., width as a percentage
or calc(100% - some padding) plus max-width: 800px) and add a simple media query
fallback for very small screens to adjust height or padding if needed; target
the `#editor-container` rule and ensure border, margin and position behavior
remain unchanged while swapping the fixed width for responsive properties.

In `@packages/document/test/view/hangul.test.ts`:
- Around line 71-73: The test title/comment is inconsistent: it says "한아" but
the assertion expects "하나"; update the test description to reflect the expected
output by renaming the it(...) title from '"한아" → ㅎㅏㄴㅏ' (or similar) to '"하나" →
ㅎㅏㄴㅏ' so the human-readable description matches the assemble(['ㅎ','ㅏ','ㄴ','ㅏ'])
expectation and intent around the assemble function's behavior.

In `@packages/document/vite.config.ts`:
- Around line 1-3: Add a brief clarifying comment at the top of this file
explaining why an empty Vite config is exported (e.g., to preserve
dev-server/default behavior while library-specific build config lives in
vite.build.ts), so future maintainers understand the presence of export default
defineConfig({}) and the relationship to vite.build.ts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a421ebc0-2d16-4242-bd51-4dc11846a68d

📥 Commits

Reviewing files that changed from the base of the PR and between 53cc2dc and 3a76f0f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • .mcp.json
  • docs/design/README.md
  • docs/design/docs.md
  • docs/tasks/active/20260320-docs-package-lessons.md
  • docs/tasks/active/20260320-docs-package-todo.md
  • package.json
  • packages/document/demo.ts
  • packages/document/index.html
  • packages/document/package.json
  • packages/document/scripts/verify-ime-browser.mjs
  • packages/document/src/index.ts
  • packages/document/src/model/document.ts
  • packages/document/src/model/types.ts
  • packages/document/src/store/memory.ts
  • packages/document/src/store/store.ts
  • packages/document/src/view/cursor.ts
  • packages/document/src/view/doc-canvas.ts
  • packages/document/src/view/editor.ts
  • packages/document/src/view/hangul.ts
  • packages/document/src/view/layout.ts
  • packages/document/src/view/selection.ts
  • packages/document/src/view/text-editor.ts
  • packages/document/src/view/theme.ts
  • packages/document/test/model/document.test.ts
  • packages/document/test/store/memory.test.ts
  • packages/document/test/view/hangul.test.ts
  • packages/document/tsconfig.json
  • packages/document/vite.build.ts
  • packages/document/vite.config.ts

Comment thread docs/design/docs.md Outdated
Comment thread docs/design/docs.md Outdated
Comment thread docs/tasks/active/20260320-docs-package-todo.md Outdated
Comment thread packages/docs/demo.ts
Comment thread packages/docs/demo.ts Outdated
Comment thread packages/docs/src/view/layout.ts
Comment thread packages/docs/src/view/selection.ts
Comment thread packages/docs/src/view/text-editor.ts
Comment thread packages/docs/src/view/text-editor.ts
Comment thread packages/docs/src/view/text-editor.ts
hackerwins and others added 11 commits March 21, 2026 11:50
Disambiguate the VitePress documentation site package from the
Canvas-based document editor package (packages/document). Also
rename the nav bar label from "Docs" to "Documentation".

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Align the root package.json script alias with the package name
for consistency with other aliases (frontend, backend, sheet, etc.).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- packages/sheet → packages/sheets (@wafflebase/sheets)
- packages/document → packages/docs (@wafflebase/docs)
- Update all imports, configs, scripts, design docs, and READMEs
- Archive task files left unchanged (historical records)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Update verify-self.mjs lane command and guard-generated-files.sh
hint message that were missed in the bulk rename.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add DocStore.snapshot() for undo stack before mutations
- Wire Ctrl+Z/Shift+Z/Ctrl+Y to undo/redo in TextEditor
- Fix innerHTML XSS in demo.ts logEvent, add clipboard error handling
- Fix deleteText infinite loop when offset at block end
- Fix arrow keys to collapse selection before moving
- Fix hangul assembler to clear selection on first composing jamo
- Update README.md project structure (sheets, docs, cli, documentation)
- Update docs/design/docs.md: IME is implemented, not deferred
- Remove duplicate docs-site.md entry in design README
- Fix hangul test description typo ("한아" → "하나")

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Layout: compute line height per-line using max font size across runs
- Layout: character-level fallback for words wider than max width
- Layout: fix hit-test offset using cumulative run lengths
- Selection: use actual line run bounds for highlight rects
- Editor: pass scrollTop to canvas render, resize canvas to layout height
- Editor: add scroll event listener for re-rendering on scroll
- IME verify: fail when no browsers are available instead of false pass

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Incorporate conditional-format multi-range changes from main.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Update paths from packages/sheet/ to packages/sheets/ in the
conditional-format-multi-range design doc merged from main.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Fix stale package references in design doc (packages/docs →
packages/document), correct task status, add responsive width
for demo page, return deep clone from MemDocStore.getBlock(),
and add clarifying comment to vite.config.ts.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The verify:entropy doc-staleness check found broken refs because
the design doc referenced packages/document/ but the actual
directory is packages/docs/. Keep @wafflebase/document as the
npm package name while using the real directory path in file refs.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
getDocument() and getBlock() now return deep clones so external
callers cannot mutate store internals without going through the
undo-tracked APIs. Added replaceDocument() for the editor to sync
its working Doc back to the store after snapshot'd mutations.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@hackerwins
hackerwins merged commit 889be82 into main Mar 21, 2026
3 checks passed
@hackerwins
hackerwins deleted the feat/document-editor branch March 21, 2026 08:16
hackerwins added a commit that referenced this pull request Mar 21, 2026
New @wafflebase/document package providing a minimal Canvas-based
rich text editor (like Google Docs) with the same architecture as
the sheet package: model/store/view layers, Vite build, Vitest.

Features:
- Document model: Document → Block[] → Inline[] hierarchy
- Inline formatting: bold, italic, underline, font size/family, color
- Block styles: alignment (left/center/right), line height, margins
- Canvas rendering with DPR-aware scaling and word-wrap layout
- Cursor with blink animation and pixel-accurate positioning
- Text selection with keyboard (Shift+Arrow) and mouse drag
- Undo/redo via snapshot-based MemDocStore
- IME composition support (compositionstart/end events)
- Software Hangul assembler for Mobile Safari (which sends raw jamo
  without composition events to hidden textareas)
- Playwright cross-browser IME test infrastructure (Chromium + WebKit)
- Demo page with toolbar and event logger for debugging

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@coderabbitai coderabbitai Bot mentioned this pull request Mar 29, 2026
10 tasks
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