Skip to content

Add table column and row resize via border drag handles#100

Merged
hackerwins merged 13 commits into
mainfrom
feat/table-resize
Apr 1, 2026
Merged

Add table column and row resize via border drag handles#100
hackerwins merged 13 commits into
mainfrom
feat/table-resize

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add column/row resize by dragging table cell borders (Google Docs style)
  • Detect mouse proximity to borders, show col-resize/row-resize cursor, drag with guideline overlay, apply on mouse-up
  • Column resize adjusts only the two adjacent column ratios; row resize sets a user-specified minimum height via new rowHeights field on TableData

Changes

  • Data model: Add rowHeights?: number[] to TableData, sync on row insert/delete
  • Doc API: New resizeColumn() (adjacent-only) and setRowHeight() methods
  • Layout: Apply user row heights as minimum floor in computeTableLayout()
  • Border detection: New table-resize.ts module with detectTableBorder() and createDragState()
  • TextEditor: Wire border detection into mouse events, drag state management, guideline via existing dragGuideline mechanism
  • Bugfixes: Add Tree guard to updateTableAttrs, fix canvas cursor targeting with data-role

Test plan

  • Unit tests for resizeColumn(), setRowHeight(), row insert/delete sync
  • Unit tests for computeTableLayout() with user row heights
  • Unit tests for detectTableBorder() (9 cases: borders, edges, intersections)
  • pnpm verify:fast passes (297 tests)
  • Manual: hover near column border → col-resize cursor
  • Manual: hover near row border → row-resize cursor
  • Manual: drag column border → guideline + resize on release
  • Manual: drag row border → guideline + resize on release

Design doc: docs/design/docs-table-resize.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Drag to resize table columns and rows with a dashed guideline preview and resize cursor; changes persist and apply across pages.
    • Per-row custom minimum heights can be set, saved, and are preserved when inserting/deleting rows.
  • Documentation

    • Added a design spec and archived task notes describing table-resize behavior and implementation guidance.
  • Tests

    • Added tests for border detection, resize behavior, and row-height persistence/syncing.

Optional field for user-specified minimum row heights in pixels.
Undefined means content-based auto height (existing behavior).
resizeColumn() adjusts only the two adjacent columns sharing a border,
keeping the total ratio sum at 1.0. setRowHeight() sets a user-specified
minimum height in pixels via a new rowHeights array on TableData.
Splice the rowHeights array in insertRow() and deleteRow() to keep it
aligned with the rows array. New rows get undefined (auto height).
Persist the updated array via updateTableAttrs so the store stays in sync.
User heights act as a floor; content height wins when it exceeds the
user value. Rows without a user height use content-based auto sizing.
detectTableBorder() checks if mouse coordinates are within 4px of a
resizable border. Column borders take priority at intersections.
createDragState() computes min/max pixel bounds for drag clamping.
Reuse the ruler's dragGuideline variable and renderPaintOnly() for
table border drag feedback. No new rendering code needed.
Detect border proximity on mousemove (cursor change), initiate drag on
mousedown, update guideline position during drag, and apply resize on
mouseup. Column resize adjusts adjacent ratios; row resize sets minimum
height.
Tree class is exported from @yorkie-js/sdk, not @yorkie-js/react.
The wrong import caused "Tree is not a constructor" at runtime when
creating a new document.
Add the same getRootTreeNode guard used by other store methods to
prevent crash when content is not yet a Tree CRDT instance.
The canvas has cursor:text set directly, which overrides the container's
cursor style. Change border hover cursor on the canvas element itself.
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Google-Docs-style table resizing: model support for per-row minimum heights, Doc APIs to resize columns and set row heights, border hit-detection and drag-state logic, editor mouse integration with a dashed guideline, layout enforcement of row minimums, and persistence via store updates.

Changes

Cohort / File(s) Summary
Design Docs
docs/design/README.md, docs/design/docs-table-resize.md
Added design doc entry and full spec describing border hit-testing, drag lifecycle, guideline rendering, model/API changes, layout integration, pagination behavior, and implementation checklist.
Model & Public API
packages/docs/src/model/types.ts, packages/docs/src/model/document.ts
Added `rowHeights?: (number
Store Implementations
packages/docs/src/store/store.ts, packages/docs/src/store/memory.ts, packages/frontend/src/app/docs/yorkie-doc-store.ts
Extended updateTableAttrs signature to accept optional rowHeights; memory/Yorkie stores persist rowHeights defensively and Yorkie serialization/deserialization added.
Layout & Renderer
packages/docs/src/view/table-layout.ts, packages/docs/src/view/editor.ts
computeTableLayout now enforces tableData.rowHeights as per-row minimums; editor canvas marked with data-role='doc-canvas' and editor wired to receive paint-only guideline updates.
Resize Detection & Drag State
packages/docs/src/view/table-resize.ts
New module: border hit-detection, drag-state creation, exported types/constants (BORDER_THRESHOLD, MIN_COLUMN_WIDTH, MIN_ROW_HEIGHT, BorderHit, BorderDragState, detectTableBorder, createDragState).
Editor Integration
packages/docs/src/view/text-editor.ts
Added borderDragState, onDragGuideline callback, getBorderDragState(), mouse handlers to detect/initiate/track/commit drags, and applyBorderDrag() to call Doc APIs and mark tables dirty.
Tests
packages/docs/test/...
packages/docs/test/model/table.test.ts, packages/docs/test/model/types.test.ts, packages/docs/test/view/table-layout.test.ts, packages/docs/test/view/table-resize.test.ts
Added tests for resizeColumn, setRowHeight, rowHeights sync on insert/delete, layout minimum behavior, and border hit-detection/precedence.
Tasks / Archive
docs/tasks/*, docs/tasks/archive/*
Added implementation TODOs and lessons-learned notes; incremented archived task counts and added archive entries for table-resize work.

Sequence Diagram

sequenceDiagram
    actor User as User (Mouse)
    participant Editor as TextEditor
    participant Detector as Table Resize Detector
    participant Layout as Table Layout
    participant Doc as Doc API
    participant Store as Store
    participant Renderer as Renderer

    User->>Editor: mousedown on table border
    Editor->>Detector: detectTableBorder(localX, localY)
    Detector->>Layout: query column/row offsets
    Detector-->>Editor: BorderHit / null
    alt Border detected
        Editor->>Editor: createDragState(hit, layout, mouse)
        Note over Editor: store startPixel, min/max bounds
    end

    User->>Editor: mousemove
    alt borderDragState active
        Editor->>Editor: clamp currentPixel
        Editor->>Editor: onDragGuideline({x|y})
        Note over Renderer: paint-only dashed guideline render
    else
        Editor->>Detector: detectTableBorder for cursor
        Editor->>Editor: update canvas cursor style
    end

    User->>Editor: mouseup
    alt borderDragState active
        Editor->>Doc: resizeColumn(...) or setRowHeight(...)
        Doc->>Store: updateTableAttrs({ cols, rowHeights? })
        Store-->>Doc: persisted
        Editor->>Renderer: trigger full re-render / layout refresh
    end
    Editor->>Editor: clear borderDragState & guideline
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I tug a border, soft and sly,
A dashed blue line slips nearby,
Columns breathe and rows grow tall,
Ratios shift at my small call,
Tables bow beneath my paw ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add table column and row resize via border drag handles' clearly and accurately describes the main feature added in this PR: interactive table resizing through border dragging.

✏️ 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/table-resize

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.

Document 6 lessons from the implementation: Yorkie SDK/React version
mismatch, bundled class identity for instanceof, multi-canvas container
selection, CSS cursor precedence, store refresh semantics, and reusing
existing dragGuideline infrastructure.
@github-actions

github-actions Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 107.9s

Lane Status Duration
sheets:build ✅ pass 13.5s
docs:build ✅ pass 6.4s
verify:fast ✅ pass 54.4s
frontend:build ✅ pass 14.7s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.3s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 12.4s

Verification: verify:integration

Result: ✅ PASS

@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: 6

🤖 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-table-resize.md`:
- Around line 157-176: The fenced code blocks showing the column width and row
height formulas are missing a language tag; update the two fences surrounding
the column-resize snippet (containing deltaRatio, newLeftRatio, newRightRatio,
minRatio, resizeColumn) and the row-height snippet (containing deltaPx,
layoutHeight, newHeight, setRowHeight) to include a language identifier such as
text or typescript (e.g., replace ``` with ```text) so markdownlint MD040 is
satisfied and optional highlighting applied.

In `@packages/docs/src/model/document.ts`:
- Around line 564-566: td.rowHeights is treated as number[] but the code
intentionally stores "unset" entries (undefined/null); remove the unsafe cast
"undefined as unknown as number" by widening the rowHeights element type across
the model and store contract (e.g., change the type used in the Document model,
any TableData/type aliases, and Store.updateTableAttrs signature to accept
number | undefined | null as appropriate) or add a normalization step that
converts persisted nulls to undefined before calling updateTableAttrs; update
all call sites (including td.rowHeights splice at the shown blockId path and the
other occurrence around lines 792-799) so the compiler recognizes and enforces
nullish checks instead of using casts.

In `@packages/docs/src/view/table-resize.ts`:
- Around line 59-71: When initializing a column drag (hit.type === 'column')
ensure we handle the case where leftColStart and rightColEnd produce an invalid
range (minPixel > maxPixel) due to adjacent columns totalling less than 2 *
MIN_COLUMN_WIDTH: detect this after computing leftColStart, rightColEnd,
minPixel and maxPixel and either abort the drag or collapse the bounds by
setting both minPixel and maxPixel to mousePixel (or otherwise early-return a
no-op drag) so the mouse-move clamp won’t force the first commit to violate
MIN_COLUMN_WIDTH; update the logic in the block that returns the column drag
object (references: MIN_COLUMN_WIDTH, leftColStart, rightColEnd, minPixel,
maxPixel, mousePixel, hit.index) accordingly.

In `@packages/docs/src/view/text-editor.ts`:
- Around line 735-751: When starting a table border drag in the mouse handler
(where resolveTableFromMouse, detectTableBorder and createDragState are used and
borderDragState/isMouseDown are set), ensure the editor captures the pointer for
the drag lifetime or installs temporary document/window mousemove/mouseup
listeners so the drag is reliably cleared if the pointer leaves the editor;
specifically, call element.setPointerCapture(event.pointerId) (or attach
document.addEventListener('mousemove'/'mouseup') and clean them up) right after
creating borderDragState and set up a removal path that clears borderDragState
and releases pointer capture (or removes the temporary listeners) on
pointerup/mouseup, and apply the same change to the other drag handling block
referenced around lines 896-943 so stale borderDragState cannot persist when the
pointer is released outside the editor.
- Around line 667-711: resolveTableFromMouse uses a tableOriginY anchored to the
table's first rendered line so localY includes inter-page gaps and can miss
row-border hits on later pages; change the Y rebasing to use the page-local
segment that actually contains the mouse similar to resolveOffsetInCellAtXY.
Locate resolveTableFromMouse, find where tablePageY/tableOriginY and localY are
computed, then determine which paginatedLayout.pages segment contains the mouse
Y (using the same row-aware math as in resolveOffsetInCellAtXY) and subtract
that page-segment origin when computing localY so comparisons against
tl.rowYOffsets and tl.totalHeight use gap-free table coordinates for
hit-testing.

In `@packages/frontend/src/app/docs/yorkie-doc-store.ts`:
- Around line 564-575: updateTableAttrs is updating block.tableData.rowHeights
but then rewrites the block using buildBlockNode which only
serializes/deserializes column widths (cols), so rowHeights is lost on Yorkie
round-trip; modify the table serialization/parsing used by buildBlockNode (and
any helper functions that convert Block/tableData to/from Yorkie nodes) to
include rowHeights in the serialized node shape and ensure parse logic restores
tableData.rowHeights so the value persisted by updateTableAttrs survives
rehydrate and remote sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f439f5cf-5b0f-4d4b-9654-76de193e7ae7

📥 Commits

Reviewing files that changed from the base of the PR and between 6cae6d5 and 490438d.

📒 Files selected for processing (15)
  • docs/design/README.md
  • docs/design/docs-table-resize.md
  • packages/docs/src/model/document.ts
  • packages/docs/src/model/types.ts
  • packages/docs/src/store/memory.ts
  • packages/docs/src/store/store.ts
  • packages/docs/src/view/editor.ts
  • packages/docs/src/view/table-layout.ts
  • packages/docs/src/view/table-resize.ts
  • packages/docs/src/view/text-editor.ts
  • packages/docs/test/model/table.test.ts
  • packages/docs/test/model/types.test.ts
  • packages/docs/test/view/table-layout.test.ts
  • packages/docs/test/view/table-resize.test.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts

Comment thread docs/design/docs-table-resize.md Outdated
Comment on lines +564 to +566
if (td.rowHeights) {
td.rowHeights.splice(atIndex, 0, undefined as unknown as number);
this.store.updateTableAttrs(blockId, { cols: td.columnWidths, rowHeights: td.rowHeights });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Give rowHeights an element type that matches its runtime contract.

Unset row heights are a first-class state here, but this path still has to smuggle them through undefined as unknown as number. Please widen the element type across the model/store contract (or normalize persisted nulls back to undefined) instead of treating rowHeights as a plain number[], so the compiler helps with the nullish checks that the layout code already depends on.

As per coding guidelines, "Use type annotations in TypeScript to improve code clarity and catch errors early."

Also applies to: 792-799

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

In `@packages/docs/src/model/document.ts` around lines 564 - 566, td.rowHeights is
treated as number[] but the code intentionally stores "unset" entries
(undefined/null); remove the unsafe cast "undefined as unknown as number" by
widening the rowHeights element type across the model and store contract (e.g.,
change the type used in the Document model, any TableData/type aliases, and
Store.updateTableAttrs signature to accept number | undefined | null as
appropriate) or add a normalization step that converts persisted nulls to
undefined before calling updateTableAttrs; update all call sites (including
td.rowHeights splice at the shown blockId path and the other occurrence around
lines 792-799) so the compiler recognizes and enforces nullish checks instead of
using casts.

Comment thread packages/docs/src/view/table-resize.ts
Comment on lines +667 to +711
private resolveTableFromMouse(e: MouseEvent): {
tableBlockId: string;
localX: number;
localY: number;
layout: import('./table-layout.js').LayoutTable;
tableOriginX: number;
tableOriginY: number;
} | null {
const rect = this.container.getBoundingClientRect();
const s = this.getScaleFactor();
const mouseX = (e.clientX - rect.left + this.container.scrollLeft) / s;
const mouseY = (e.clientY - rect.top - this.getCanvasOffsetTop()) / s + this.container.scrollTop / s;

const layout = this.getLayout();
const paginatedLayout = this.getPaginatedLayout();
const { margins } = paginatedLayout.pageSetup;
const pageX = getPageXOffset(paginatedLayout, this.getCanvasWidth());

for (const lb of layout.blocks) {
if (lb.block.type !== 'table' || !lb.layoutTable) continue;
const tl = lb.layoutTable;
const blockIndex = layout.blocks.indexOf(lb);

let tablePageY = 0;
for (const page of paginatedLayout.pages) {
for (const pl of page.lines) {
if (pl.blockIndex === blockIndex && pl.lineIndex === 0) {
tablePageY = getPageYOffset(paginatedLayout, page.pageIndex) + pl.y;
break;
}
}
if (tablePageY !== 0) break;
}

const tableOriginX = pageX + margins.left;
const tableOriginY = tablePageY;
const localX = mouseX - tableOriginX;
const localY = mouseY - tableOriginY;

if (localX >= 0 && localX <= tl.totalWidth && localY >= 0 && localY <= tl.totalHeight) {
return { tableBlockId: lb.block.id, localX, localY, layout: tl, tableOriginX, tableOriginY };
}
}
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use page-local Y coordinates for row-border hit-testing.

localY is anchored to the table’s first rendered line here. Once a table breaks across pages, the inter-page gap gets folded into that value while layout.rowYOffsets and totalHeight stay gap-free table coordinates, so hover/drag can miss the correct row border on later pages. Rebase Y against the page segment that actually contains the hovered rows, like the row-aware math in resolveOffsetInCellAtXY().

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

In `@packages/docs/src/view/text-editor.ts` around lines 667 - 711,
resolveTableFromMouse uses a tableOriginY anchored to the table's first rendered
line so localY includes inter-page gaps and can miss row-border hits on later
pages; change the Y rebasing to use the page-local segment that actually
contains the mouse similar to resolveOffsetInCellAtXY. Locate
resolveTableFromMouse, find where tablePageY/tableOriginY and localY are
computed, then determine which paginatedLayout.pages segment contains the mouse
Y (using the same row-aware math as in resolveOffsetInCellAtXY) and subtract
that page-segment origin when computing localY so comparisons against
tl.rowYOffsets and tl.totalHeight use gap-free table coordinates for
hit-testing.

Comment thread packages/docs/src/view/text-editor.ts
Comment thread packages/frontend/src/app/docs/yorkie-doc-store.ts Outdated

@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: 2

🧹 Nitpick comments (1)
docs/tasks/archive/2026/03/20260331-table-resize-todo.md (1)

895-895: Optional wording polish at Line 895.

Replace “exactly what we want” with a less emphatic phrasing (e.g., “matches the intended behavior”) for cleaner technical tone.

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

In `@docs/tasks/archive/2026/03/20260331-table-resize-todo.md` at line 895, Update
the phrasing at the noted sentence to be less emphatic: replace “exactly what we
want” with a more neutral phrase like “matches the intended behavior” (or
similar) in the sentence that explains that reusing dragGuideline → paint()
dashed line rendering from the ruler produces full-height/full-width dashed
lines; no changes to dragGuideline or paint() code are required—only adjust the
wording in the documentation line referencing the dashed line rendering.
🤖 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/archive/2026/03/20260331-table-resize-todo.md`:
- Line 15: The section heading "Task 1: Data Model — Add `rowHeights` to
`TableData`" currently uses an H3 (###) directly after top-level content which
triggers markdownlint MD001; change that heading to H2 (use "## Task 1: Data
Model — Add `rowHeights` to `TableData`") or insert an intermediate H2 before
the group of task sections so each task can remain H3, ensuring consistent
heading hierarchy across the document.
- Line 36: The doc uses a machine-specific absolute path in the run command ("cd
/Users/hackerwins/Development/wafflebase/wafflesheets && pnpm --filter
`@wafflebase/docs` test -- --run test/model/types.test.ts"); update all
occurrences (e.g., the command string present at the shown line and the other
listed lines) to use a repo-relative form such as "cd $PWD && pnpm --filter
`@wafflebase/docs` test -- --run test/model/types.test.ts" or simply "pnpm
--filter `@wafflebase/docs` test -- --run test/model/types.test.ts" so the
instruction runs from the repository root and is portable across machines.

---

Nitpick comments:
In `@docs/tasks/archive/2026/03/20260331-table-resize-todo.md`:
- Line 895: Update the phrasing at the noted sentence to be less emphatic:
replace “exactly what we want” with a more neutral phrase like “matches the
intended behavior” (or similar) in the sentence that explains that reusing
dragGuideline → paint() dashed line rendering from the ruler produces
full-height/full-width dashed lines; no changes to dragGuideline or paint() code
are required—only adjust the wording in the documentation line referencing the
dashed line rendering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b63d003-f220-4189-bfac-ea9c72ac18ac

📥 Commits

Reviewing files that changed from the base of the PR and between 490438d and b5c9692.

📒 Files selected for processing (4)
  • docs/tasks/README.md
  • docs/tasks/archive/2026/03/20260331-table-resize-lessons.md
  • docs/tasks/archive/2026/03/20260331-table-resize-todo.md
  • docs/tasks/archive/README.md
✅ Files skipped from review due to trivial changes (3)
  • docs/tasks/README.md
  • docs/tasks/archive/README.md
  • docs/tasks/archive/2026/03/20260331-table-resize-lessons.md


---

### Task 1: Data Model — Add `rowHeights` to `TableData`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix heading level increment at Line 15.

Line 15 starts at ### immediately after top-level content; markdownlint MD001 is correct here. Use ## for task sections (or add an intermediate ## heading before them).

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 15-15: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

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

In `@docs/tasks/archive/2026/03/20260331-table-resize-todo.md` at line 15, The
section heading "Task 1: Data Model — Add `rowHeights` to `TableData`" currently
uses an H3 (###) directly after top-level content which triggers markdownlint
MD001; change that heading to H2 (use "## Task 1: Data Model — Add `rowHeights`
to `TableData`") or insert an intermediate H2 before the group of task sections
so each task can remain H3, ensuring consistent heading hierarchy across the
document.


- [x] **Step 2: Run test to verify it passes (baseline)**

Run: `cd /Users/hackerwins/Development/wafflebase/wafflesheets && pnpm --filter @wafflebase/docs test -- --run test/model/types.test.ts`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use repo-relative commands instead of a machine-specific absolute path.

The repeated cd /Users/hackerwins/... makes the run steps non-portable for other contributors. Prefer commands that run from repo root (or use $PWD) so copy/paste works universally.

Suggested doc edit
-Run: `cd /Users/hackerwins/Development/wafflebase/wafflesheets && pnpm --filter `@wafflebase/docs` test -- --run test/model/types.test.ts`
+Run: `pnpm --filter `@wafflebase/docs` test -- --run test/model/types.test.ts`

Also applies to: 54-54, 99-99, 124-124, 157-157, 222-222, 282-282, 307-307, 355-355, 376-376, 494-494, 608-608, 858-858, 899-899, 911-911

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

In `@docs/tasks/archive/2026/03/20260331-table-resize-todo.md` at line 36, The doc
uses a machine-specific absolute path in the run command ("cd
/Users/hackerwins/Development/wafflebase/wafflesheets && pnpm --filter
`@wafflebase/docs` test -- --run test/model/types.test.ts"); update all
occurrences (e.g., the command string present at the shown line and the other
listed lines) to use a repo-relative form such as "cd $PWD && pnpm --filter
`@wafflebase/docs` test -- --run test/model/types.test.ts" or simply "pnpm
--filter `@wafflebase/docs` test -- --run test/model/types.test.ts" so the
instruction runs from the repository root and is portable across machines.

- Critical: Serialize/deserialize rowHeights in Yorkie round-trip via
  buildBlockNode and treeNodeToBlock (comma-separated attribute)
- Major: Widen rowHeights type to (number | undefined)[] across model,
  store, and Yorkie store — remove unsafe casts
- Major: Add document-level mousemove/mouseup listeners during border
  drag so release outside the editor clears drag state
- Minor: Guard against minPixel > maxPixel when adjacent columns are
  too narrow — collapse to no-op drag
- Minor: Add language tags to fenced code blocks in design doc

@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: 1

🧹 Nitpick comments (1)
packages/docs/src/model/document.ts (1)

792-801: Consider adding bounds and value validation.

The method writes to td.rowHeights[rowIndex] without verifying rowIndex is within [0, td.rows.length). If an invalid index is passed, it would write past the array bounds. Additionally, negative height values would be persisted (though the layout engine in table-layout.ts would ignore them).

🛡️ Proposed fix with bounds check
 setRowHeight(blockId: string, rowIndex: number, height: number): void {
   const block = this.getBlock(blockId);
   const td = block.tableData!;
+  if (rowIndex < 0 || rowIndex >= td.rows.length) {
+    return; // Invalid row index
+  }
   if (!td.rowHeights) {
     td.rowHeights = new Array(td.rows.length).fill(undefined);
   }
   td.rowHeights[rowIndex] = height;
   this.store.updateTableAttrs(blockId, { cols: td.columnWidths, rowHeights: td.rowHeights });
   this.refresh();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/model/document.ts` around lines 792 - 801, The setRowHeight
method currently writes td.rowHeights[rowIndex] without validating inputs; add
checks in setRowHeight(blockId: string, rowIndex: number, height: number) to (1)
ensure block and block.tableData (td) exist, (2) verify rowIndex is an integer
within 0 <= rowIndex < td.rows.length (return or throw a clear error if out of
range), and (3) validate height is a finite non-negative number (either clamp to
0 or reject invalid values). Also ensure td.rowHeights is initialized to length
td.rows.length before assignment and only call
this.store.updateTableAttrs(blockId, { cols: td.columnWidths, rowHeights:
td.rowHeights }) and this.refresh() after validation/sanitization completes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/docs/src/model/document.ts`:
- Around line 783-790: The resizeColumn method can write past the end of
td.columnWidths when colIndex is the last index; add a defensive bounds check in
resizeColumn (in class/method resizeColumn): retrieve block via getBlock and td
= block.tableData, then verify colIndex >= 0 and colIndex + 1 <
td.columnWidths.length (or clamp indices) and if invalid return early (no
updates or refresh), otherwise assign td.columnWidths[colIndex] and
td.columnWidths[colIndex + 1], call this.store.updateTableAttrs(blockId, { cols:
td.columnWidths }) and this.refresh(); this prevents sparse arrays/invalid
writes and keeps existing caller behavior intact.

---

Nitpick comments:
In `@packages/docs/src/model/document.ts`:
- Around line 792-801: The setRowHeight method currently writes
td.rowHeights[rowIndex] without validating inputs; add checks in
setRowHeight(blockId: string, rowIndex: number, height: number) to (1) ensure
block and block.tableData (td) exist, (2) verify rowIndex is an integer within 0
<= rowIndex < td.rows.length (return or throw a clear error if out of range),
and (3) validate height is a finite non-negative number (either clamp to 0 or
reject invalid values). Also ensure td.rowHeights is initialized to length
td.rows.length before assignment and only call
this.store.updateTableAttrs(blockId, { cols: td.columnWidths, rowHeights:
td.rowHeights }) and this.refresh() after validation/sanitization completes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6e103d2-94bf-4273-b12e-53ca3f9962be

📥 Commits

Reviewing files that changed from the base of the PR and between b5c9692 and 6684439.

📒 Files selected for processing (9)
  • docs/design/docs-table-resize.md
  • packages/docs/src/model/document.ts
  • packages/docs/src/model/types.ts
  • packages/docs/src/store/memory.ts
  • packages/docs/src/store/store.ts
  • packages/docs/src/view/table-resize.ts
  • packages/docs/src/view/text-editor.ts
  • packages/docs/test/view/table-layout.test.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/docs/src/model/types.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/docs/src/store/store.ts
  • packages/docs/src/store/memory.ts
  • packages/docs/test/view/table-layout.test.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts
  • docs/design/docs-table-resize.md
  • packages/docs/src/view/table-resize.ts
  • packages/docs/src/view/text-editor.ts

Comment on lines +783 to +790
resizeColumn(blockId: string, colIndex: number, leftRatio: number, rightRatio: number): void {
const block = this.getBlock(blockId);
const td = block.tableData!;
td.columnWidths[colIndex] = leftRatio;
td.columnWidths[colIndex + 1] = rightRatio;
this.store.updateTableAttrs(blockId, { cols: td.columnWidths });
this.refresh();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing bounds check for colIndex + 1 could corrupt table state.

If colIndex equals td.columnWidths.length - 1 (the last column), writing to td.columnWidths[colIndex + 1] creates a sparse array or writes to an invalid index. While the caller in text-editor.ts may prevent this, a defensive check here would prevent silent corruption.

🛡️ Proposed fix with bounds check and early return
 resizeColumn(blockId: string, colIndex: number, leftRatio: number, rightRatio: number): void {
   const block = this.getBlock(blockId);
   const td = block.tableData!;
+  if (colIndex < 0 || colIndex + 1 >= td.columnWidths.length) {
+    return; // Cannot resize: no adjacent column to the right
+  }
   td.columnWidths[colIndex] = leftRatio;
   td.columnWidths[colIndex + 1] = rightRatio;
   this.store.updateTableAttrs(blockId, { cols: td.columnWidths });
   this.refresh();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
resizeColumn(blockId: string, colIndex: number, leftRatio: number, rightRatio: number): void {
const block = this.getBlock(blockId);
const td = block.tableData!;
td.columnWidths[colIndex] = leftRatio;
td.columnWidths[colIndex + 1] = rightRatio;
this.store.updateTableAttrs(blockId, { cols: td.columnWidths });
this.refresh();
}
resizeColumn(blockId: string, colIndex: number, leftRatio: number, rightRatio: number): void {
const block = this.getBlock(blockId);
const td = block.tableData!;
if (colIndex < 0 || colIndex + 1 >= td.columnWidths.length) {
return; // Cannot resize: no adjacent column to the right
}
td.columnWidths[colIndex] = leftRatio;
td.columnWidths[colIndex + 1] = rightRatio;
this.store.updateTableAttrs(blockId, { cols: td.columnWidths });
this.refresh();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/model/document.ts` around lines 783 - 790, The resizeColumn
method can write past the end of td.columnWidths when colIndex is the last
index; add a defensive bounds check in resizeColumn (in class/method
resizeColumn): retrieve block via getBlock and td = block.tableData, then verify
colIndex >= 0 and colIndex + 1 < td.columnWidths.length (or clamp indices) and
if invalid return early (no updates or refresh), otherwise assign
td.columnWidths[colIndex] and td.columnWidths[colIndex + 1], call
this.store.updateTableAttrs(blockId, { cols: td.columnWidths }) and
this.refresh(); this prevents sparse arrays/invalid writes and keeps existing
caller behavior intact.

@hackerwins
hackerwins merged commit e189de3 into main Apr 1, 2026
3 checks passed
@hackerwins
hackerwins deleted the feat/table-resize branch April 1, 2026 12:47
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