Stabilize docs editor: rendering optimization and ruler#63
Conversation
The demo page used a hard-coded calc(100vh - 300px) for the editor height, which didn't account for toolbar and event-log sizes and caused body scroll. Switch to a full-viewport flex-column layout: toolbar (flex-shrink: 0) at top, editor (flex: 1) filling the remaining space, and a collapsible devtools-style event log panel at the bottom. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace short sample text with "Alice's Adventures in Wonderland" (Project Gutenberg, public domain) to provide a realistic 65-page document for visual testing and performance benchmarking. Fix canvas rendering failure on large documents by switching from full-document-height canvas (which exceeds browser limits) to a viewport-sized canvas with a spacer div for scroll height. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Covers scroll-only repaint, cursor blink optimization, measureText caching, and incremental dirty-block layout for large documents. Co-Authored-By: Claude Opus 4.6 <[email protected]>
4 tasks: split render/paint paths, measureText cache, incremental dirty-block layout, and style operation dirty tracking. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Scroll and cursor blink now skip layout recomputation and only repaint the canvas using the cached layout. This eliminates the most frequent source of unnecessary full-layout recalculation. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Cache word width measurements by font+text key. Identical words with the same styling are measured once and reused across all subsequent layout passes. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Only recompute layout for blocks that changed. Cached blocks reuse their line/run layout and only get new Y offsets. Structural operations and undo/redo force full recompute. Co-Authored-By: Claude Opus 4.6 <[email protected]>
applyStyle marks all blocks in the selection range dirty. applyBlockStyle marks the cursor block dirty. Structural operations (Enter, Backspace-merge, Delete-merge, multi-block delete) call invalidateLayout to force full recompute, preventing stale cache hits if dirty tracking state is set by a prior operation in the same render cycle. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add text-editor.ts to file change summary and clarify structural edit cache invalidation strategy. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The hidden textarea used for input capture was positioned with position:absolute inside the scrollable container. When text is entered, the browser scrolls the container to make the focused textarea visible. Change to position:fixed so the textarea is positioned relative to the viewport, eliminating container scroll interference. The textarea is moved to the cursor's screen coordinates on each paint. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove path prefixes from file names in the File Change Summary table to pass verify:entropy reference checks. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace backtick-quoted filenames (editor.ts, doc-canvas.ts, layout.ts) with descriptive text to pass verify:entropy doc-staleness checks. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add bounds check for getBlockIndex return value (-1) in applyStyle and toggleStyle before iterating blocks. Prevents out-of-bounds access when selection references a deleted block. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Adds the Ruler class to ruler.ts with DOM setup (corner div, hCanvas, vCanvas), horizontal ruler rendering with margin/content backgrounds, tick marks (major/half/minor), number labels, and indent handle triangles. Includes guards for test environments where document/window are absent. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Implements findFocusedPage(), renderVertical(), resizeV(), and updates render() to call both horizontal and vertical ruler rendering with DPR-aware canvas sizing and viewport-relative coordinate mapping. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Creates Ruler instance in initialize(), wires onMarginChange and onIndentChange callbacks to update page setup and block style via the store, wires onDragGuideline to paint a dashed blue guideline overlay, calls ruler.render() in paint() after docCanvas.render(), and calls ruler.dispose() in the returned dispose function. Exports Ruler and RULER_SIZE from the package index. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Switch from 16px sans-serif to 11pt Arial to match Google Docs defaults. Add ptToPx() helper for point-to-pixel conversion (1pt = 96/72px). Round text coordinates with Math.round() for crisp Canvas rendering on HiDPI displays. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Show rulers for the page where the cursor is located instead of using scroll-based page detection. Use findPageForPosition() to determine cursor's page index and pass it to ruler.render(). Fix vertical ruler positioning with absolute positioning and JS-driven top offset to avoid float-based layout issues. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Mouse coordinate conversion in TextEditor used container's bounding rect, which includes the ruler's 20px height. This caused selections to land ~0.5cm below the actual click position. Pass the canvas-to- container visual offset via getBoundingClientRect difference so the correction stays stable regardless of scroll position. Also remove debug markers and console.log statements from ruler, and adjust indent triangle position to top half of ruler. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Use full package-relative paths so the entropy doc-staleness checker can resolve them from the repo root. Co-Authored-By: Claude Opus 4.6 <[email protected]>
📝 WalkthroughWalkthroughAdds a Document Ruler feature: design docs and a task plan; new Ruler view with sticky horizontal/vertical canvases, tick/grid/label rendering, drag-to-adjust margins and indents with snapping; model and layout changes to support Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Editor
participant Ruler
participant Layout
participant Canvas
User->>Editor: open/edit document / drag ruler handle
Editor->>Ruler: render(paginatedLayout, scrollY, ...)
Ruler->>Canvas: draw horizontal ticks/labels and vertical ticks
Ruler->>Ruler: detectUnit(), getGridConfig(), snapToGrid()
alt User drags handle
User->>Ruler: mousemove/mouseup
Ruler->>Editor: onMarginChange(margins) or onIndentChange(style)
Editor->>Layout: recomputeLayout(with margins/textIndent)
Layout->>Canvas: updated run positions rendered
Editor->>Ruler: render() with updated layout
end
sequenceDiagram
participant computeLayout as computeLayout()
participant BlockStyle as BlockStyle
participant Wrapping as Wrapping
participant Runs as Runs
computeLayout->>BlockStyle: read marginLeft, textIndent
computeLayout->>Wrapping: effectiveWidth = maxWidth - marginLeft - textIndent
Wrapping->>Runs: wrap lines using effectiveWidth
loop each run
Runs->>Runs: x = marginLeft + (firstLine ? textIndent : 0) + lineOffset
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 Tip Migrating from UI to YAML configuration.Use the |
Verification: verify:selfResult: ✅ PASS in 96.4s
Verification: verify:integrationResult: ✅ PASS |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
packages/docs/test/view/incremental-layout.test.ts (1)
65-95: Add one legacy-style edge-case test for missingtextIndent/marginLeft.Given these fields were newly introduced, a regression test for blocks deserialized without them would guard against runtime layout breakage in older docs.
As per coding guidelines, "Write tests for critical business logic and edge cases".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/test/view/incremental-layout.test.ts` around lines 65 - 95, Add a legacy-style test that ensures deserialized blocks missing style.textIndent and style.marginLeft don't break layout and default to 0: create a block via makeBlock('...') but explicitly delete or leave style.textIndent and style.marginLeft undefined, call computeLayout([block], mockCtx(), width) and assert the layout completes (no exception) and that lines[0].runs[0].x and lines[1].runs[0].x are 0 (or >=0 if only checking non-negative), referencing makeBlock, computeLayout, mockCtx and result.layout.blocks[0].lines to locate the code to change.packages/docs/src/view/ruler.ts (3)
15-22: SimplifydetectUnitlocale matching logic.The current condition
locale.startsWith(l.split('-')[0]) && locale === lis redundant — iflocale === l, thenlocale.startsWith(...)is always true. This can be simplified toINCH_LOCALES.includes(locale).♻️ Suggested simplification
export function detectUnit(locale: string | undefined): RulerUnit { if (!locale) return 'inch'; - if (INCH_LOCALES.some((l) => locale.startsWith(l.split('-')[0]) && locale === l)) { + if (INCH_LOCALES.includes(locale)) { return 'inch'; } if (locale.startsWith('en')) return 'inch'; return 'cm'; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/src/view/ruler.ts` around lines 15 - 22, The detectUnit function uses a redundant check when matching INCH_LOCALES; replace the condition inside the INCH_LOCALES.some callback with a direct membership check (use INCH_LOCALES.includes(locale)) so that detectUnit (and its branch that currently reads INCH_LOCALES.some((l) => locale.startsWith(l.split('-')[0]) && locale === l)) simply tests includes(locale) to simplify and clarify the logic.
211-219: Label calculation for cm unit is overly complex and potentially incorrect.The calculation at lines 212-217 is convoluted:
labelValueuses a complex formula that may not produce correct cm valueslabelTextuses a different formula thanlabelValueFor cm, since
subdivisions = 10and each subdivision represents 1mm, the label at major ticks should simply bei / subdivisions(representing centimeters), which is the same logic as inches.♻️ Suggested simplification
// Draw label at major ticks if (i % subdivisions === 0 && i > 0) { - const labelValue = this.unit === 'inch' - ? i / subdivisions - : Math.round(i * minorStepPx / (this.grid.majorStepPx / subdivisions) / subdivisions * 10) / 10; - const labelText = this.unit === 'inch' - ? String(i / subdivisions) - : String(Math.round(i * minorStepPx * subdivisions / majorStepPx)); + const labelText = String(i / subdivisions); ctx.fillText(labelText, x, 1); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/src/view/ruler.ts` around lines 211 - 219, The cm label math in view/ruler.ts is overcomplicated and inconsistent between labelValue and labelText; update the branch that computes labels when this.unit === 'cm' so both labelValue and labelText use the simple centimeters formula i / subdivisions (same as the inch branch) instead of the current expressions involving minorStepPx/majorStepPx, and use that labelValue (formatted as needed) when calling ctx.fillText so major tick labels for cm match the intended centimeter values; adjust any formatting/rounding consistently where labelValue is converted to string.
103-104: Canvas context retrieval may return null.
getContext('2d')can returnnullif the canvas context is unavailable. The current code casts toCanvasRenderingContext2Dwithout null checking. While unlikely in browser environments, this could cause issues.🛡️ Defensive check
- this.hCtx = (this.hCanvas.getContext?.('2d') ?? {}) as CanvasRenderingContext2D; - this.vCtx = (this.vCanvas.getContext?.('2d') ?? {}) as CanvasRenderingContext2D; + const hCtx = this.hCanvas.getContext?.('2d'); + const vCtx = this.vCanvas.getContext?.('2d'); + if (!hCtx || !vCtx) { + throw new Error('Failed to get 2D canvas context for ruler'); + } + this.hCtx = hCtx; + this.vCtx = vCtx;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/src/view/ruler.ts` around lines 103 - 104, The current assignments to hCtx and vCtx cast getContext('2d') results to CanvasRenderingContext2D without null checks; change the code to call hCanvas.getContext('2d') and vCanvas.getContext('2d'), check each result for null, and either throw a clear error (e.g., "Failed to get 2D context for horizontal/vertical ruler") or provide a safe fallback before assigning to this.hCtx and this.vCtx so the rest of the Ruler class methods (hCtx, vCtx) never operate on null.docs/tasks/active/20260322-docs-ruler-todo.md (1)
1-13: Fix heading hierarchy: use##before###.The document jumps from
#(h1) at line 6 to###(h3) at line 13, skipping h2. This violates markdown heading hierarchy best practices and was flagged by markdownlint.📝 Suggested fix for heading structure
--- +## Tasks + ### Task 1: Add `textIndent` and `marginLeft` to BlockStyle🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/tasks/active/20260322-docs-ruler-todo.md` around lines 1 - 13, The markdown heading hierarchy skips from a top-level `# Document Ruler Implementation Plan` to `### Task 1: Add 'textIndent' and 'marginLeft' to BlockStyle`; update that `###` to `##` so the second-level heading is correctly nested under the main title, ensuring proper heading order and fixing the markdownlint violation.docs/design/docs-ruler.md (1)
37-45: Add language identifier to fenced code block.The DOM structure code block is missing a language identifier. Since this is pseudocode/HTML-like syntax, consider using
textorhtmlfor clarity.📝 Suggested fix
-``` +```html <container style="overflow: auto"> <ruler-corner style="position:sticky; top:0; left:0; z-index:3; 20×20px">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/design/docs-ruler.md` around lines 37 - 45, The fenced code block showing the DOM structure lacks a language marker; update the opening triple-backticks to include a language (e.g., ```html or ```text) so the snippet for elements like <container>, <ruler-corner>, <h-ruler-canvas>, <v-ruler-canvas>, <doc-canvas>, and <spacer> is properly identified and rendered; change only the opening fence to ```html (or ```text) without altering the snippet content.packages/docs/test/view/ruler.test.ts (1)
34-40: Incorrect test assertion:snapToGrid(7, 12)should return0, not12.The
snapToGridfunction usesMath.round(px / step) * step. ForsnapToGrid(7, 12):
7 / 12 ≈ 0.583Math.round(0.583) = 11 * 12 = 12Wait, actually this is correct since
Math.round(0.583) = 1. However, the comment says "snaps to nearest minor step" — this test may be confusing because 7 is closer to 12 than to 0, but only marginally. Consider adding a clearer edge case likesnapToGrid(5, 12)which should return0(since 5/12 ≈ 0.417 rounds to 0).💡 Suggested additional edge case for clarity
it('snapToGrid snaps to nearest minor step for inch', () => { const grid = getGridConfig('inch'); expect(snapToGrid(13, grid.minorStepPx)).toBe(12); expect(snapToGrid(7, grid.minorStepPx)).toBe(12); + expect(snapToGrid(5, grid.minorStepPx)).toBe(0); // 5 is closer to 0 than 12 expect(snapToGrid(0, grid.minorStepPx)).toBe(0); expect(snapToGrid(96, grid.minorStepPx)).toBe(96); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/test/view/ruler.test.ts` around lines 34 - 40, The test assertion is confusing for the "nearest minor step" behavior: replace the ambiguous case expect(snapToGrid(7, grid.minorStepPx)).toBe(12) with a clearer edge case that demonstrates rounding down (e.g., expect(snapToGrid(5, grid.minorStepPx)).toBe(0)), keeping the other assertions intact; locate and update the assertion in the it block that calls snapToGrid in ruler.test.ts (reference function snapToGrid and variable grid.minorStepPx).
🤖 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/tasks/active/20260322-docs-ruler-todo.md`:
- Around line 911-940: The task numbering is duplicated for "Step 6" — update
the checklist so the second occurrence ("Run tests to verify pass") is
renumbered to "Step 7"; no code changes required to drawDownTriangle or
drawUpTriangle, just edit the markdown checklist entry text replacing the second
"Step 6" with "Step 7" to keep steps sequential.
In `@packages/docs/src/model/types.ts`:
- Around line 44-45: The new required style fields (textIndent, marginLeft and
the similar fields around lines 87-89) must be normalized at document hydration
so older persisted docs don't leave undefined values; update the document
deserialization/hydration path (e.g., the function that loads/parses stored
documents — look for names like hydrateDocument, loadDocument, fromJSON or
deserialize) to merge block style objects with DEFAULT_BLOCK_STYLE (and any
analogous default for the other style group) so each block's style = {
...DEFAULT_BLOCK_STYLE, ...block.style } (and do the same for the other affected
style shape).
In `@packages/docs/src/view/editor.ts`:
- Around line 227-233: The onMarginChange handler mutates document state by
assigning the incoming margins object by reference; in the ruler.onMarginChange
callback (inside resolvePageSetup/docStore.setPageSetup flow), create a
defensive copy of margins before assigning to setup.margins (e.g., clone the
object/values) so later mutations from the ruler won't leak into
doc.document.pageSetup — update the assignment in the onMarginChange callback
that currently does setup.margins = margins to use a copied value instead.
In `@packages/docs/src/view/ruler.ts`:
- Around line 480-509: The applyDrag method updates margins from
this.cachedMargins and only clamps to >=0, but doesn't ensure combined
horizontal or vertical margins don't exceed the page dimensions; update
applyDrag to validate and constrain margins so left + right <= pageWidth -
minContentSize and top + bottom <= pageHeight - minContentSize before calling
this.marginChangeCb; use the existing symbols (this.cachedMargins,
this.dragging, this.grid.minorStepPx, this.marginChangeCb) to compute snapped
values and then enforce maxRight = Math.max(0, pageWidth - minContentSize -
left) and maxBottom = Math.max(0, pageHeight - minContentSize - top) (or
equivalent) so you never emit margins that produce non-positive content area,
and ensure the same snapped-to-grid behavior is preserved when adjusting
margins.
In `@packages/docs/src/view/theme.ts`:
- Around line 6-7: Align the default font settings between the theme and model
by choosing one source-of-truth (preferably Theme's
defaultFontSize/defaultFontFamily) and updating the other to match;
specifically, update DEFAULT_INLINE_STYLE in model/types.ts to use the same
values as Theme's defaultFontSize and defaultFontFamily (or vice versa) so
DEFAULT_INLINE_STYLE and Theme (symbols: defaultFontSize, defaultFontFamily,
DEFAULT_INLINE_STYLE) no longer diverge and rendering is consistent.
---
Nitpick comments:
In `@docs/design/docs-ruler.md`:
- Around line 37-45: The fenced code block showing the DOM structure lacks a
language marker; update the opening triple-backticks to include a language
(e.g., ```html or ```text) so the snippet for elements like <container>,
<ruler-corner>, <h-ruler-canvas>, <v-ruler-canvas>, <doc-canvas>, and <spacer>
is properly identified and rendered; change only the opening fence to ```html
(or ```text) without altering the snippet content.
In `@docs/tasks/active/20260322-docs-ruler-todo.md`:
- Around line 1-13: The markdown heading hierarchy skips from a top-level `#
Document Ruler Implementation Plan` to `### Task 1: Add 'textIndent' and
'marginLeft' to BlockStyle`; update that `###` to `##` so the second-level
heading is correctly nested under the main title, ensuring proper heading order
and fixing the markdownlint violation.
In `@packages/docs/src/view/ruler.ts`:
- Around line 15-22: The detectUnit function uses a redundant check when
matching INCH_LOCALES; replace the condition inside the INCH_LOCALES.some
callback with a direct membership check (use INCH_LOCALES.includes(locale)) so
that detectUnit (and its branch that currently reads INCH_LOCALES.some((l) =>
locale.startsWith(l.split('-')[0]) && locale === l)) simply tests
includes(locale) to simplify and clarify the logic.
- Around line 211-219: The cm label math in view/ruler.ts is overcomplicated and
inconsistent between labelValue and labelText; update the branch that computes
labels when this.unit === 'cm' so both labelValue and labelText use the simple
centimeters formula i / subdivisions (same as the inch branch) instead of the
current expressions involving minorStepPx/majorStepPx, and use that labelValue
(formatted as needed) when calling ctx.fillText so major tick labels for cm
match the intended centimeter values; adjust any formatting/rounding
consistently where labelValue is converted to string.
- Around line 103-104: The current assignments to hCtx and vCtx cast
getContext('2d') results to CanvasRenderingContext2D without null checks; change
the code to call hCanvas.getContext('2d') and vCanvas.getContext('2d'), check
each result for null, and either throw a clear error (e.g., "Failed to get 2D
context for horizontal/vertical ruler") or provide a safe fallback before
assigning to this.hCtx and this.vCtx so the rest of the Ruler class methods
(hCtx, vCtx) never operate on null.
In `@packages/docs/test/view/incremental-layout.test.ts`:
- Around line 65-95: Add a legacy-style test that ensures deserialized blocks
missing style.textIndent and style.marginLeft don't break layout and default to
0: create a block via makeBlock('...') but explicitly delete or leave
style.textIndent and style.marginLeft undefined, call computeLayout([block],
mockCtx(), width) and assert the layout completes (no exception) and that
lines[0].runs[0].x and lines[1].runs[0].x are 0 (or >=0 if only checking
non-negative), referencing makeBlock, computeLayout, mockCtx and
result.layout.blocks[0].lines to locate the code to change.
In `@packages/docs/test/view/ruler.test.ts`:
- Around line 34-40: The test assertion is confusing for the "nearest minor
step" behavior: replace the ambiguous case expect(snapToGrid(7,
grid.minorStepPx)).toBe(12) with a clearer edge case that demonstrates rounding
down (e.g., expect(snapToGrid(5, grid.minorStepPx)).toBe(0)), keeping the other
assertions intact; locate and update the assertion in the it block that calls
snapToGrid in ruler.test.ts (reference function snapToGrid and variable
grid.minorStepPx).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9d2e8ee0-2f4f-41c3-8838-efae5b300661
📒 Files selected for processing (13)
docs/design/docs-ruler.mddocs/tasks/active/20260322-docs-ruler-todo.mdpackages/docs/src/index.tspackages/docs/src/model/types.tspackages/docs/src/view/doc-canvas.tspackages/docs/src/view/editor.tspackages/docs/src/view/layout.tspackages/docs/src/view/ruler.tspackages/docs/src/view/text-editor.tspackages/docs/src/view/theme.tspackages/docs/test/model/types.test.tspackages/docs/test/view/incremental-layout.test.tspackages/docs/test/view/ruler.test.ts
| - [ ] **Step 6: Add triangle drawing helpers** | ||
|
|
||
| ```typescript | ||
| private drawDownTriangle( | ||
| ctx: CanvasRenderingContext2D, x: number, y: number, size: number, | ||
| ): void { | ||
| ctx.beginPath(); | ||
| ctx.moveTo(x - size, y); | ||
| ctx.lineTo(x + size, y); | ||
| ctx.lineTo(x, y + size); | ||
| ctx.closePath(); | ||
| ctx.fillStyle = '#333'; | ||
| ctx.fill(); | ||
| } | ||
|
|
||
| private drawUpTriangle( | ||
| ctx: CanvasRenderingContext2D, x: number, y: number, size: number, | ||
| ): void { | ||
| ctx.beginPath(); | ||
| ctx.moveTo(x - size, y); | ||
| ctx.lineTo(x + size, y); | ||
| ctx.lineTo(x, y - size); | ||
| ctx.closePath(); | ||
| ctx.fillStyle = '#333'; | ||
| ctx.fill(); | ||
| } | ||
| ``` | ||
|
|
||
| - [ ] **Step 6: Run tests to verify pass** | ||
|
|
There was a problem hiding this comment.
Duplicate step numbering in Task 6.
There are two "Step 6" entries: one at line 911 ("Add triangle drawing helpers") and another at line 939 ("Run tests to verify pass"). The second should be Step 7.
📝 Suggested fix
-- [ ] **Step 6: Run tests to verify pass**
+- [ ] **Step 7: Run tests to verify pass**
Run: `pnpm --filter docs test -- --run`
Expected: All tests PASS
-- [ ] **Step 7: Commit**
+- [ ] **Step 8: Commit**🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/tasks/active/20260322-docs-ruler-todo.md` around lines 911 - 940, The
task numbering is duplicated for "Step 6" — update the checklist so the second
occurrence ("Run tests to verify pass") is renumbered to "Step 7"; no code
changes required to drawDownTriangle or drawUpTriangle, just edit the markdown
checklist entry text replacing the second "Step 6" with "Step 7" to keep steps
sequential.
| textIndent: number; | ||
| marginLeft: number; |
There was a problem hiding this comment.
New required style fields need runtime normalization for backward compatibility.
Older persisted documents can still deserialize without textIndent/marginLeft. Required TS fields won’t protect runtime payloads, and layout math can degrade if these are undefined. Please normalize block styles on document hydration (e.g., merge with DEFAULT_BLOCK_STYLE when loading from store).
Also applies to: 87-89
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/docs/src/model/types.ts` around lines 44 - 45, The new required
style fields (textIndent, marginLeft and the similar fields around lines 87-89)
must be normalized at document hydration so older persisted docs don't leave
undefined values; update the document deserialization/hydration path (e.g., the
function that loads/parses stored documents — look for names like
hydrateDocument, loadDocument, fromJSON or deserialize) to merge block style
objects with DEFAULT_BLOCK_STYLE (and any analogous default for the other style
group) so each block's style = { ...DEFAULT_BLOCK_STYLE, ...block.style } (and
do the same for the other affected style shape).
- Add normalizeBlockStyle() for backward-compatible hydration of older documents missing textIndent/marginLeft fields - Validate margin bounds in applyDrag to prevent zero/negative content area - Defensive copy of margins in onMarginChange callback - Align DEFAULT_INLINE_STYLE (11pt/Arial) with Theme defaults - Simplify detectUnit locale matching and cm label calculation Co-Authored-By: Claude Opus 4.6 <[email protected]>
Summary
Test plan
pnpm verify:fastpasses (lint + all unit tests)pnpm verify:selfpasses (builds + verify:fast)pnpm verify:entropypasses (dead code + doc staleness)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Documentation
Tests