Skip to content

Refactor worksheet collaboration model boundary#29

Merged
hackerwins merged 5 commits into
mainfrom
codex/worksheet-collaboration-boundary
Mar 14, 2026
Merged

Refactor worksheet collaboration model boundary#29
hackerwins merged 5 commits into
mainfrom
codex/worksheet-collaboration-boundary

Conversation

@hackerwins

@hackerwins hackerwins commented Mar 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • refactor the worksheet collaboration boundary around the canonical cells / rowOrder / colOrder schema and split Yorkie-specific structure helpers out of the shared sheet package
  • add structural concurrency coverage with a table-driven matrix, real two-user Yorkie tests, and deferred repro slices for the remaining same-index delete/delete gap
  • add backend Yorkie worksheet-shape migration tooling, including a pure migration helper, admin CLI, and tests for the restored-data edge cases discovered during rehearsal
  • document the local restore rehearsal, hackerwins local-access prep, and the production deployment runbook for migrating existing Yorkie documents

Testing

  • pnpm verify:fast
  • local restored-data rehearsal: DATABASE_URL=postgresql://wafflebase:wafflebase@localhost:5432/wafflebase_prod_restore YORKIE_RPC_ADDR=http://localhost:8080 pnpm --filter @wafflebase/backend migrate:yorkie:worksheet-shape --all
  • restored problematic document idempotency: 3f54103b-9ba0-446b-a13b-96331d6a4199 migrated once as legacy-tabbed and then re-ran as current with changed=0

Notes

  • the known non-goal remains concurrent delete row N vs delete row N / delete column N vs delete column N
  • restored local documents and datasources were regrouped into the hackerwins workspace to make browser validation easier

Summary by CodeRabbit

  • New Features

    • Canonical spreadsheet collaboration model and migration tooling for Yorkie-backed documents.
  • Bug Fixes

    • Improved structural concurrency for row/column edits and fixed local backend dev-start behavior.
  • Documentation

    • Extensive new/updated design and task docs covering collaboration, migration runbooks, restore rehearsals, test architecture, and worksheet boundaries.
  • Tests

    • Added concurrency test infra, matrix and worksheet-structure tests, and two-user Yorkie repro helpers.
  • Chores

    • Updated dev scripts and added tooling/dependencies for Yorkie migration and document copying.

@coderabbitai

coderabbitai Bot commented Mar 14, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors spreadsheet model to stable-identity worksheets keyed by row/col IDs, reorganizes sheet package exports, adds frontend/backend worksheet axis and structure helpers, implements Yorkie worksheet shape migration CLI with tests, removes legacy frontend migration code, updates API and tests to use worksheet abstractions, and improves concurrency test coverage.

Changes

Cohort / File(s) Summary
Design & Tasks
docs/design/README.md, docs/design/datasource.md, docs/design/frontend.md, docs/design/spreadsheet-collaboration.md, docs/tasks/*
Added new spreadsheet-collaboration design doc; removed legacy auto-migration info; updated frontend design doc with canonical worksheet shape; added many task and lessons docs on CRDT structural concurrency, migrations, restores, testing, and rollout.
Backend: migration & CLI scripts
packages/backend/src/yorkie/worksheet-shape-migration.ts, .../worksheet-shape-migration.spec.ts, packages/backend/scripts/migrate-yorkie-worksheet-shape.{ts,js,d.ts}, packages/backend/scripts/copy-yorkie-documents.{ts,js,d.ts}
Introduced new migration module to convert legacy Yorkie worksheet shapes to canonical format with tests; added CLI scripts for migration and document copying between Yorkie instances with robust snapshot normalization and detailed reporting.
Backend: API, types & config
packages/backend/src/api/v1/cells.controller.ts, packages/backend/src/yorkie/yorkie.types.ts, packages/backend/package.json, packages/backend/tsconfig*.json
Cells controller updated to use worksheet helper APIs instead of direct sheet access; backend re-exports spreadsheet types from @wafflebase/sheet; new backend build and migration CLI scripts added with tsx dependency; TypeScript configuration improved with incremental build info and include/exclude settings; Jest config updated with module alias for sheet.
Sheet package: public model & grid primitives
packages/sheet/src/index.ts, packages/sheet/src/model/workbook/worksheet-document.ts, .../worksheet-grid.ts, .../worksheet-record.ts, packages/sheet/package.json
Added canonical sheet model types and constructors for worksheets and spreadsheets; introduced in-memory worksheet grid primitives (cell get/set, entries, keys) and safe record key helpers; updated package.json exports to point to built distribution artifacts; expanded public exports for worksheet manipulation and types.
Sheet package: internal refactor & import updates
many packages/sheet/src/... files and tests
Refactored internal module paths to core and worksheet submodules with updated import paths for model, formula, pivot, view, and test files; no functional changes except improved code organization; tests updated to new import locations.
Frontend: YorkieStore and worksheet mutators
packages/frontend/src/app/spreadsheet/yorkie-store.ts, .../yorkie-worksheet-axis.ts, .../yorkie-worksheet-structure.ts
Rewrote YorkieStore internals to operate through worksheet abstraction helpers; implemented axis and sheet structure mutation helpers for row/column insert/delete/move; delegated complex formula, merge, chart remapping to shared worksheet mutation utilities exported for reuse.
Frontend: remove legacy migration & consolidate types
packages/frontend/src/app/documents/migration.ts (removed), packages/frontend/src/app/documents/document-detail.tsx, packages/frontend/src/types/worksheet.ts, packages/frontend/src/types/users.ts
Removed legacy migration utilities and tests from frontend; document initialization simplified to use createWorksheet(); frontend type declarations replaced by consolidated re-exports from @wafflebase/sheet; imports adjusted to use type-only imports where appropriate.
Frontend: cell access modernization
packages/frontend/src/app/spreadsheet/chart-utils.ts, .../pivot/use-pivot-table.ts, .../sheet-view.tsx, packages/frontend/src/app/documents/document-detail.tsx
Replaced direct cell map accesses (sheet.sheet) with public worksheet helpers (getWorksheetCell, writeWorksheetCell, replaceWorksheetCells, parseRef) across chart computations, pivot table building, and sheet-view resolution paths.
Frontend tests & helpers
packages/frontend/tests/*, packages/frontend/tests/helpers/two-user-yorkie.ts, packages/frontend/tests/resolve-hooks.mjs
Added Yorkie concurrency tests including concurrency characterization and repros; introduced two-user Yorkie concurrent editing test helper; new worksheet-structure tests validate complex sheet shifts and moves; removed legacy migration tests; updated resolver to fallback to sheet source index; added @yorkie-js/sdk as dependency.
Sheet tests & concurrency matrix
packages/sheet/test/helpers/concurrency-case-table.ts, .../concurrency-driver.ts, packages/sheet/test/sheet/concurrency-matrix.test.ts, many packages/sheet/test/**
Added comprehensive concurrency case definitions and serial order driver tests; bolstered concurrency coverage; updated many tests to new core/worksheet import paths; added unit tests for worksheet structure mutations (shift and move).
Dev workflows & package scripts
package.json, packages/backend/package.json, packages/sheet/package.json, packages/frontend/vite.config.ts
Root dev script updated to run backend start:dev; backend package adds prestart:dev and migrate-yorkie-worksheet-shape scripts; adds tsx dependency for TS script execution; sheet package main/module/types now point to built dist files with exports field added; frontend Vite config adds alias for @wafflebase/sheet.
Miscellaneous small updates
packages/frontend/src/app/spreadsheet/yorkie-store.ts, d.ts files, tsconfig, test helpers
Added d.ts placeholder files; updated tsconfig build info file; numerous import path fixes across tests and source files; minor dependency and helper adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Migration CLI
    participant Prisma as Prisma/Postgres
    participant Migrator as migrateYorkieWorksheetShape
    participant Yorkie as Yorkie Server

    CLI->>Prisma: Query document IDs (--document/--all/--limit)
    Prisma-->>CLI: Return document list
    CLI->>Yorkie: Attach document
    Yorkie-->>CLI: Return root snapshot
    CLI->>Migrator: Detect & migrate root shape
    Migrator-->>CLI: { kind, changed, document }
    alt changed
        CLI->>Yorkie: Replace root with migrated document & sync
        Yorkie-->>CLI: Ack / synced
        CLI->>Yorkie: Detach
    else unchanged
        CLI-->>CLI: Skip update
    end
    CLI->>Prisma: Print migration summary
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through rows and columns new,

Gave every cell a steady name.
Yorkie danced and snapshots flew,
Migrations hummed, tests learned the game.
A carrot-cheer — the grid speaks true!

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/worksheet-collaboration-boundary

@github-actions

github-actions Bot commented Mar 14, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 97.6s

Lane Status Duration
sheet:build ✅ pass 13.0s
verify:fast ✅ pass 47.7s
frontend:build ✅ pass 13.9s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.7s
cli:build ✅ pass 1.6s
verify:entropy ✅ pass 15.9s
verify:browser ✅ pass 0.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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sheet/src/index.ts (1)

139-177: ⚠️ Potential issue | 🔴 Critical

Re-export type-only symbols with export type.

These symbols are imported as types but re-exported as runtime values in the single export { ... } block (lines 136–255), causing SyntaxError: Export 'AggregateFunction' is not defined in module and similar errors for all type-only bindings.

Split the export block: use export type { Grid, Cell, CellStyle, ConditionalFormatOperator, ConditionalFormatRule, ConditionalFormatStyle, BorderPreset, TextAlign, VerticalAlign, NumberFormat, FilterOperator, FilterCondition, FilterState, HiddenState, AggregateFunction, PivotFieldSort, PivotField, PivotValueField, PivotFilterField, PivotTableDefinition, PivotRecord, GroupNode, PivotCellType, PivotCell, PivotResult, Ref, Sref, Range, MergeSpan, Direction, Axis, SelectionType, GridResolver, Store, LayoutRect, Theme, RangeStylePatch, LocaleFormatPreview, FormatValueOptions, InferInputOptions, InferredInput, InferredInputFormat, ChartType, SheetChart, WorksheetFilterState, Worksheet, TabType, SheetKind, TabMeta, SpreadsheetDocument } for type-only exports, and keep the remaining symbols in export { ... } for runtime values.

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

In `@packages/sheet/src/index.ts` around lines 139 - 177, The current single
export block re-exports many bindings that are type-only, causing runtime export
errors; split the block in packages/sheet/src/index.ts into two exports: an
export type { ... } listing all type-only symbols (e.g. Grid, Cell, CellStyle,
ConditionalFormatOperator, ConditionalFormatRule, ConditionalFormatStyle,
BorderPreset, TextAlign, VerticalAlign, NumberFormat, FilterOperator,
FilterCondition, FilterState, HiddenState, AggregateFunction, PivotFieldSort,
PivotField, PivotValueField, PivotFilterField, PivotTableDefinition,
PivotRecord, GroupNode, PivotCellType, PivotCell, PivotResult, Ref, Sref, Range,
MergeSpan, Direction, Axis, SelectionType, GridResolver, Store, LayoutRect,
Theme, RangeStylePatch, LocaleFormatPreview, FormatValueOptions,
InferInputOptions, InferredInput, InferredInputFormat, ChartType, SheetChart,
WorksheetFilterState, Worksheet, TabType, SheetKind, TabMeta,
SpreadsheetDocument) and keep a separate runtime export { ... } for actual
values (e.g. MemStore, ReadOnlyStore, findEdgeWithIndex, Grid, Cell
classes/constructors or functions) so that only type-only imports use export
type and runtime symbols remain in the normal export list.
🧹 Nitpick comments (18)
packages/backend/scripts/migrate-yorkie-worksheet-shape.d.ts (1)

1-1: Clarify the purpose of this empty declaration file.

An empty export {} in a .d.ts file is unusual. If this is a CLI script that shouldn't be imported as a library, consider whether this declaration file is needed at all, or add a comment explaining its purpose (e.g., satisfying module resolution or preventing accidental imports).

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

In `@packages/backend/scripts/migrate-yorkie-worksheet-shape.d.ts` at line 1, The
file migrate-yorkie-worksheet-shape.d.ts currently only contains an empty
"export {}" which is unclear; either delete this declaration file if it serves
no purpose, or replace it with a short explanatory comment and a clearer intent
(e.g., explain it exists to force module scope / prevent accidental imports or
to satisfy TypeScript module resolution), or change it to a proper declaration
(declare module '...' { /* types */ }) if it is intended to provide typings;
update the file migrate-yorkie-worksheet-shape.d.ts accordingly and ensure the
change matches the intended use of the CLI script or module.
packages/sheet/src/view/spreadsheet.ts (1)

1-12: Make the core-types import type-only.

Every symbol imported from ../model/core/types is used exclusively in type positions (parameter types, return types, and type annotations), so converting to a type-only import accurately reflects the new core/worksheet boundary.

♻️ Proposed fix
-import {
+import type {
   Axis,
   BorderPreset,
   CellStyle,
   ConditionalFormatRule,
   FilterCondition,
   GridResolver,
   Range,
   Ref,
   SelectionType,
 } from '../model/core/types';
 import { Sheet } from '../model/worksheet/sheet';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/src/view/spreadsheet.ts` around lines 1 - 12, The import from
../model/core/types should be changed to a type-only import because all symbols
(Axis, BorderPreset, CellStyle, ConditionalFormatRule, FilterCondition,
GridResolver, Range, Ref, SelectionType) are used only for typing; update the
import line in spreadsheet.ts to use an "import type { ... } from
'../model/core/types';" form so the core/worksheet boundary is preserved and
runtime bundling won't include these types.
packages/sheet/src/view/formulabar.ts (1)

2-2: Use a type-only import for Sheet.

Sheet is only referenced in type positions (lines 12 and 80), so converting to import type removes an unnecessary runtime dependency between the view and worksheet layers.

♻️ Proposed fix
-import { Sheet } from '../model/worksheet/sheet';
+import type { Sheet } from '../model/worksheet/sheet';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/src/view/formulabar.ts` at line 2, Replace the runtime import
of Sheet with a type-only import: change "import { Sheet } from
'../model/worksheet/sheet';" to "import type { Sheet } from
'../model/worksheet/sheet';" in formulabar.ts, ensuring that all references to
Sheet (e.g., the type annotations used in the form bar functions/variables)
remain type-only and that no runtime usage of Sheet exists in the file so the
compiler emits no runtime import.
docs/tasks/active/20260314-yorkie-worksheet-shape-migration-lessons.md (1)

6-6: Hyphenate compound adjective for clarity.

Consider changing “side-effect free” to “side-effect-free” in Line 6.

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

In `@docs/tasks/active/20260314-yorkie-worksheet-shape-migration-lessons.md` at
line 6, The phrase "side-effect free" should be hyphenated as the compound
adjective "side-effect-free"; update the text in the document where the label
sentence contains "side-effect free" so it reads "side-effect-free" to clarify
the compound modifier.
docs/tasks/active/20260313-crdt-structural-concurrency-retrospective-todo.md (1)

3-4: Use hyphenated compound modifiers for readability.

Consider “stable-grid-based” and “visual-index-based” in these sections for cleaner technical prose.

Also applies to: 36-39

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

In `@docs/tasks/active/20260313-crdt-structural-concurrency-retrospective-todo.md`
around lines 3 - 4, Replace non-hyphenated compound modifiers with hyphenated
forms for clarity: change occurrences of "stable grid based" to
"stable-grid-based" and "visual index based" to "visual-index-based" throughout
the document (including the phrases in the current section and the lines
referenced as 36-39); update any variants like "stable-grid based" or
"visual-index based" to the fully hyphenated form to ensure consistent, readable
technical prose.
docs/tasks/active/20260313-backend-dev-start-failure-todo.md (1)

67-70: Make verification commands context-stable.

Line 69 switches to a bare tsc --build ... command, which is ambiguous outside packages/backend. Consider using the same pnpm --filter @wafflebase/backend exec ... pattern (or explicitly state the working directory) so the repro steps are deterministic.

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

In `@docs/tasks/active/20260313-backend-dev-start-failure-todo.md` around lines 67
- 70, The second verification step uses a bare "tsc --build tsconfig.build.json
--force --listEmittedFiles" which is ambiguous; update that line to either use
the same pnpm wrapper (pnpm --filter `@wafflebase/backend` exec tsc --build
tsconfig.build.json --force --listEmittedFiles) or explicitly state "run from
packages/backend" so the commands are deterministic and context-stable in the
repro steps.
packages/frontend/tests/helpers/two-user-yorkie.ts (2)

63-86: Add exhaustiveness check for operation kinds.

The switch statement handles known operation kinds but doesn't have a default case. If a new operation kind is added to ConcurrencyOp, this function will silently do nothing rather than failing fast.

♻️ Proposed fix to add exhaustiveness check
 async function applyStoreOp(store: YorkieStore, op: ConcurrencyOp): Promise<void> {
   switch (op.kind) {
     case "set-data": {
       const ref = parseRef(op.ref);
       const cell = op.value.startsWith("=")
         ? { f: op.value }
         : { v: op.value };
       await store.set(ref, cell);
       return;
     }
     case "insert-rows":
       await store.shiftCells("row", op.index, op.count ?? 1);
       return;
     case "delete-rows":
       await store.shiftCells("row", op.index, -(op.count ?? 1));
       return;
     case "insert-columns":
       await store.shiftCells("column", op.index, op.count ?? 1);
       return;
     case "delete-columns":
       await store.shiftCells("column", op.index, -(op.count ?? 1));
       return;
+    default: {
+      const _exhaustive: never = op;
+      throw new Error(`Unknown operation kind: ${(_exhaustive as ConcurrencyOp).kind}`);
+    }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/tests/helpers/two-user-yorkie.ts` around lines 63 - 86, The
switch in applyStoreOp handling ConcurrencyOp kinds is non-exhaustive; add a
default branch that fails fast for unknown kinds (so new variants don't silently
no-op). Update applyStoreOp to include a default case that asserts
exhaustiveness (e.g., cast op to never or throw an Error) and surface the
unexpected op.kind; reference applyStoreOp, ConcurrencyOp, parseRef,
YorkieStore.set and YorkieStore.shiftCells to locate where to add the
default/exhaustive check.

46-48: Consider improving type safety for YorkieStore instantiation.

The as never cast bypasses type checking. This works but could mask type mismatches if YorkieStore constructor signature changes.

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

In `@packages/frontend/tests/helpers/two-user-yorkie.ts` around lines 46 - 48, The
helper createStore currently silences type checking by casting doc to never;
update it to preserve type-safety by making createStore generic and casting via
unknown (or typing the parameter to the YorkieStore constructor type). For
example, change function createStore(doc: T) to a generic signature (e.g.,
function createStore<T>(doc: T): YorkieStore and cast doc as unknown as
ConstructorParamType) so the YorkieStore instantiation (new YorkieStore(...)) no
longer uses "as never" and mismatched shapes are caught by the compiler; locate
the createStore function and the YorkieStore constructor usage to apply this
change.
packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts (1)

72-93: Consider documenting the 1-based index convention.

The function uses Math.max(0, index - 1) to convert from 1-based to 0-based indices. This convention should be documented in the function signature or a comment to prevent confusion for callers.

📝 Suggested documentation
+/**
+ * Insert new rows or columns into the worksheet.
+ * `@param` ws - The worksheet to modify
+ * `@param` axis - Whether to insert rows or columns
+ * `@param` index - 1-based position to insert at (1 = before first element)
+ * `@param` count - Number of rows/columns to insert
+ */
 export function insertYorkieWorksheetAxis(
   ws: Worksheet,
   axis: Axis,
   index: number,
   count: number,
 ): void {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts` around lines
72 - 93, Add clear documentation that the parameter index is 1-based and the
function converts it to 0-based via Math.max(0, index - 1); update the function
comment or JSDoc for insertYorkieWorksheetAxis to state the 1-based indexing
convention (for both row and col axes) and note that ensureAxisLength and the
splice call expect the converted 0-based index, so callers should pass 1-based
positions (use index = 1 for the first position).
packages/backend/scripts/copy-yorkie-documents.js (1)

104-126: Document mongosh dependency requirement.

The fetchExistingYorkieKeys function shells out to mongosh. If the tool isn't installed, execFileSync will throw with a potentially confusing error. Consider adding a pre-flight check or documenting this dependency in the usage text.

💡 Proposed usage text update (apply to source .ts file)
 function usage() {
     return `Usage:
   pnpm --filter `@wafflebase/backend` exec tsx scripts/copy-yorkie-documents.ts \\
     --database-url <postgres-url> \\
     --mongo-url <mongo-url> \\
     --project-public-key <public-key> \\
     --source-rpc-addr <source-rpc-addr> \\
     --source-api-key <source-api-key> \\
     --target-rpc-addr <target-rpc-addr> \\
-    [--limit <count>] [--document <id> ...]`;
+    [--limit <count>] [--document <id> ...]
+
+Requires: mongosh (MongoDB Shell) to be installed and in PATH.`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/scripts/copy-yorkie-documents.js` around lines 104 - 126,
The function fetchExistingYorkieKeys currently calls mongosh via execFileSync
and will throw a confusing error if mongosh isn't installed; add a pre-flight
check before invoking execFileSync (e.g., call execFileSync('mongosh',
['--version']) or spawnSync to verify availability and a zero exit code) and
replace the raw exception with a clear, user-facing error that explains the
missing mongosh dependency and points to installation instructions; update any
usage/help text in the same file to document the mongosh requirement and refer
to the new check so users see an actionable message instead of the low-level
exception.
packages/sheet/src/model/workbook/worksheet-grid.ts (1)

88-89: Use for...of to avoid linter warning about callback return values.

The forEach callbacks implicitly return the result of Map.set(), which triggers Biome's useIterableCallbackReturn rule. While functionally correct, switching to for...of silences the linter and is arguably more readable.

♻️ Proposed refactor
-  rowOrder.forEach((rowId, index) => rowIndex.set(rowId, index + 1));
-  colOrder.forEach((colId, index) => colIndex.set(colId, index + 1));
+  for (const [index, rowId] of rowOrder.entries()) {
+    rowIndex.set(rowId, index + 1);
+  }
+  for (const [index, colId] of colOrder.entries()) {
+    colIndex.set(colId, index + 1);
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/src/model/workbook/worksheet-grid.ts` around lines 88 - 89,
The linter flags useIterableCallbackReturn because rowOrder.forEach and
colOrder.forEach callbacks return Map.set(); replace both forEach uses with
explicit for...of loops to iterate arrays and call rowIndex.set(rowId, index +
1) and colIndex.set(colId, index + 1) inside the loop bodies (use an index
counter or Array.prototype.entries() in the for...of) so that rowOrder,
colOrder, rowIndex.set, and colIndex.set remain the same but no callback return
value is produced.
packages/backend/scripts/copy-yorkie-documents.ts (2)

370-373: Add error handling for individual document copy failures.

If copyDocument throws for one document, the entire loop aborts and remaining documents are not processed. Consider catching errors per-document to continue with others and report failures at the end.

🛡️ Proposed error handling improvement
+    const failures: Array<{ key: string; error: unknown }> = [];
     for (const key of existingKeys) {
-      await copyDocument(sourceClient, targetClient, key);
-      console.log(`copied ${key}`);
+      try {
+        await copyDocument(sourceClient, targetClient, key);
+        console.log(`copied ${key}`);
+      } catch (error) {
+        console.error(`failed to copy ${key}:`, error instanceof Error ? error.message : error);
+        failures.push({ key, error });
+      }
     }
+
+    if (failures.length > 0) {
+      console.log(`\nFailed to copy ${failures.length} document(s).`);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/scripts/copy-yorkie-documents.ts` around lines 370 - 373,
The loop over existingKeys currently awaits copyDocument(sourceClient,
targetClient, key) directly so a thrown error aborts the whole process; wrap the
per-key call in a try/catch inside the for loop (the loop that iterates
existingKeys and calls copyDocument) to catch and log errors for that key
(include the key and error message), push failed keys/errors into a local
failures array, continue processing remaining keys, and after the loop report a
summary (e.g., log or throw a single aggregated error) if failures is non-empty
so callers can see which documents failed.

362-362: Consider using a Set for O(1) membership checks.

existingKeys is an array, and Array.includes() is O(n) per call. Converting to a Set would make the filter operation more efficient, especially with many documents.

♻️ Proposed optimization
-    const missingKeys = candidateKeys.filter((key) => !existingKeys.includes(key));
+    const existingKeySet = new Set(existingKeys);
+    const missingKeys = candidateKeys.filter((key) => !existingKeySet.has(key));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/scripts/copy-yorkie-documents.ts` at line 362, The filter
that builds missingKeys uses Array.includes on existingKeys which is O(n) per
check; change to create a Set from existingKeys (e.g., const existingKeySet =
new Set(existingKeys)) and then use existingKeySet.has(key) inside the
candidateKeys.filter callback to get O(1) membership checks and improve
performance (update the reference in the filter that currently uses
existingKeys.includes).
packages/sheet/src/model/workbook/worksheet-record.ts (1)

71-81: Silent fallback for malformed keys may hide bugs.

When the separator is not found, returning { rowId: '', colId: '' } silently hides malformed input. Callers may not expect empty strings and could produce unexpected behavior. Consider whether throwing an error or returning null would be safer for debugging.

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

In `@packages/sheet/src/model/workbook/worksheet-record.ts` around lines 71 - 81,
The function parseWorksheetCellKey silently returns empty strings when
WorksheetCellKeySeparator isn't found which can hide malformed input; update
parseWorksheetCellKey to fail fast by throwing a descriptive Error (include the
invalid key and the expected WorksheetCellKeySeparator) instead of returning
{rowId: '', colId: ''}, and update any callers or types to handle the thrown
error (or alternatively change the return type to nullable and return null
consistently if you prefer non-throwing behavior); locate parseWorksheetCellKey
and replace the fallback branch with a clear error throw (or null return) and
ensure tests/call sites handle the new behavior.
packages/backend/src/yorkie/worksheet-shape-migration.spec.ts (1)

252-258: Consider adding more unsupported shape test cases.

The current test only covers a simple object with unrecognized keys. Consider adding edge cases like arrays, primitives, or nested structures that don't match any expected shape.

💡 Additional test cases to consider
it('throws for array roots', () => {
  expect(() => migrateYorkieWorksheetShape([])).toThrow(
    'Unsupported Yorkie spreadsheet document shape',
  );
});

it('throws for primitive roots', () => {
  expect(() => migrateYorkieWorksheetShape('string' as never)).toThrow();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/yorkie/worksheet-shape-migration.spec.ts` around lines
252 - 258, Add extra negative test cases for migrateYorkieWorksheetShape to
cover more unsupported shapes: include at least one test that passes an array
root (e.g., []), one that passes a primitive root (e.g., 'string' cast as
never), and optionally a nested unexpected structure, each asserting they throw
'Unsupported Yorkie spreadsheet document shape' (or at least throw); update the
spec around the existing it('throws for unsupported shapes') block to add these
new it(...) cases referencing migrateYorkieWorksheetShape so the function's
validation covers arrays, primitives, and nested invalid inputs.
packages/sheet/test/helpers/concurrency-driver.ts (2)

24-45: Add exhaustiveness check for operation kinds.

The switch statement handles all current operation kinds but lacks a default case. If new operation kinds are added to ConcurrencyOp, this function would silently do nothing instead of failing fast.

♻️ Add exhaustiveness check
 export async function applyConcurrencyOp(
   sheet: Sheet,
   op: ConcurrencyOp,
 ): Promise<void> {
   switch (op.kind) {
     case 'set-data':
       await sheet.setData(parseRef(op.ref), op.value);
       return;
     case 'insert-rows':
       await sheet.insertRows(op.index, op.count ?? 1);
       return;
     case 'delete-rows':
       await sheet.deleteRows(op.index, op.count ?? 1);
       return;
     case 'insert-columns':
       await sheet.insertColumns(op.index, op.count ?? 1);
       return;
     case 'delete-columns':
       await sheet.deleteColumns(op.index, op.count ?? 1);
       return;
+    default: {
+      const _exhaustive: never = op;
+      throw new Error(`Unknown operation kind: ${(_exhaustive as ConcurrencyOp).kind}`);
+    }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/test/helpers/concurrency-driver.ts` around lines 24 - 45, The
switch in applyConcurrencyOp (function) covers current ConcurrencyOp kinds but
lacks an exhaustive default; add an explicit exhaustiveness check so new
ConcurrencyOp variants fail fast: either add a default branch that throws a
descriptive Error (e.g., `throw new Error('Unhandled ConcurrencyOp kind: ' + (op
as any).kind)`) or introduce/consume a utility like assertNever(value: never)
and call it in the default case to ensure the compiler enforces exhaustiveness
for ConcurrencyOp and surface unhandled kinds at runtime/compile time.

89-94: JSON.stringify comparison may have ordering issues.

Using JSON.stringify for snapshot comparison works but is sensitive to property ordering. If ConcurrencySnapshot properties are added in different orders, semantically equal snapshots would fail comparison.

💡 Consider deep equality check

For robustness, consider using a deep equality function or sorting keys before stringifying:

export function snapshotMatchesOneOf(
  actual: ConcurrencySnapshot,
  candidates: ConcurrencySnapshot[],
): boolean {
  const normalize = (obj: unknown) => JSON.stringify(obj, Object.keys(obj as object).sort());
  return candidates.some((candidate) => normalize(candidate) === normalize(actual));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/test/helpers/concurrency-driver.ts` around lines 89 - 94, The
snapshotMatchesOneOf function currently compares snapshots with JSON.stringify
which can fail if object key order differs; update snapshotMatchesOneOf (working
with ConcurrencySnapshot) to perform a stable deep-equality comparison instead
of raw stringify — either by normalizing objects with a deterministic
JSON.stringify (use a normalize helper that sorts Object.keys(obj) in the
replacer) before comparing, or call a reliable deepEqual (e.g. lodash/isEqual)
to compare actual against each candidate; ensure the helper handles nested
objects consistently and use that in the candidates.some check.
packages/frontend/tests/app/spreadsheet/chart-utils.test.ts (1)

45-63: Use the shared document factory in this fixture helper.

buildDoc() hand-assembles the workbook envelope even though the canonical createSpreadsheetDocument() helper now owns those defaults. Reusing that factory will keep this test data aligned with future document-schema changes instead of maintaining a parallel constructor here.

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

In `@packages/frontend/tests/app/spreadsheet/chart-utils.test.ts` around lines 45
- 63, The buildDoc helper manually constructs the spreadsheet envelope; replace
that with the shared factory createSpreadsheetDocument to keep test fixtures
aligned with canonical defaults—create the worksheet via createWorksheet(),
write cells with writeWorksheetCell(parseRef(...)), then call
createSpreadsheetDocument to produce the full document envelope and attach the
worksheet (or pass it into the factory if supported); update buildDoc to return
the result of createSpreadsheetDocument (or the factory result after assigning
tabs, tabOrder, and sheets) instead of hand-assembling the object so you avoid
duplicating schema defaults.
🤖 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/20260314-local-hackerwins-access-todo.md`:
- Line 45: The verification query currently selects PII (email); update the SQL
in the documented command so it only returns non-PII fields used for
ownership/workspace verification (select id, username from "User" order by id
asc) — locate the command string containing `select id, username, email from
"User" order by id asc;` and remove `email` from the SELECT list, keeping
quoting and the rest of the psql invocation unchanged.

In `@docs/tasks/active/20260314-local-prod-restore-todo.md`:
- Around line 77-82: The docs embed concrete credentials in commands that call
the copy-yorkie-documents.ts script and DATABASE_URL; update the examples to use
environment variable placeholders instead of literals — replace the
--project-public-key and --source-api-key flags passed to
scripts/copy-yorkie-documents.ts with --project-public-key
"$YORKIE_PROJECT_PUBLIC_KEY" and --source-api-key "$SOURCE_YORKIE_API_KEY", and
replace hard-coded DB credentials in DATABASE_URL values with
postgresql://$DB_USER:$DB_PASSWORD@localhost:5432/<DB_NAME> (also keep
YORKIE_RPC_ADDR and any other addresses as variables where appropriate).

In `@packages/backend/scripts/copy-yorkie-documents.js`:
- Around line 164-166: Add a short inline comment above the target client
construction (the sdk_1.default.Client instantiation creating targetClient using
options.targetRpcAddr) stating that the target Yorkie server is assumed to not
require authentication and that no --target-api-key is parsed; note that if
target-server authentication is later required the script will need to accept
and pass an apiKey to the Client. Keep the comment concise and reference the
absence of apiKey handling so future maintainers know why no credential is
provided.

In `@packages/backend/scripts/migrate-yorkie-worksheet-shape.js`:
- Around line 126-137: The summary.byKind object is missing the 'current-flat'
key so summary.byKind['current-flat'] will be undefined; update the
initialization of the summary constant (the summary object with
processed/changed/unchanged and byKind) to include a "'current-flat': 0" entry
alongside current, 'initialized-empty', 'legacy-flat', and 'legacy-tabbed' so
counts for current-flat are tracked correctly.
- Around line 112-119: The compiled summarizeKinds function is missing the
`current-flat` entry compared to the TypeScript source; update the
implementation in summarizeKinds to include the template
`current-flat=${summary.byKind['current-flat']}` (referencing the summarizeKinds
function and the summary.byKind map) and then regenerate the compiled JS output
(rebuild/transpile the TypeScript) so the .js matches the .ts changes.

In `@packages/backend/src/yorkie/worksheet-shape-migration.ts`:
- Around line 302-314: isLegacyWorksheetShape currently only checks for 'sheet',
'rowHeights', 'colWidths', 'frozenRows', and 'frozenCols', so LegacyWorksheet
objects that only contain optional properties like 'merges' or 'charts' are
misclassified as unsupported; update isLegacyWorksheetShape to also detect
'merges' and 'charts' (keeping the isRecord check) so that objects matching the
LegacyWorksheet shape — which migrateLegacyWorksheet already handles — are
correctly recognized and passed to migration.

In `@packages/sheet/package.json`:
- Around line 7-16: The package exports map currently points "require" at the
UMD bundle file (./dist/wafflebase-sheet.js); change the exports entry for
"require" to reference the actual CommonJS artifact produced by the build
(./dist/wafflebase-sheet.cjs.js) so the package with "type": "module" exposes an
explicit CJS file for require consumers—update the "." exports object's
"require" property accordingly (search for the exports block and the "require"
key in package.json).

In `@packages/sheet/src/model/workbook/worksheet-document.ts`:
- Line 148: initialSpreadsheetDocument is a single shared mutable value (created
once via createSpreadsheetDocument()) and should not be exported as a mutable
seed; replace the exported constant with a factory that returns a fresh document
each call (e.g. change export const initialSpreadsheetDocument =
createSpreadsheetDocument(); to export function getInitialSpreadsheetDocument()
{ return createSpreadsheetDocument(); } or export const
getInitialSpreadsheetDocument = () => createSpreadsheetDocument();), or
alternatively export a deeply frozen template by calling Object.freeze on a deep
clone; update call sites to invoke getInitialSpreadsheetDocument() where they
previously referenced initialSpreadsheetDocument.

---

Outside diff comments:
In `@packages/sheet/src/index.ts`:
- Around line 139-177: The current single export block re-exports many bindings
that are type-only, causing runtime export errors; split the block in
packages/sheet/src/index.ts into two exports: an export type { ... } listing all
type-only symbols (e.g. Grid, Cell, CellStyle, ConditionalFormatOperator,
ConditionalFormatRule, ConditionalFormatStyle, BorderPreset, TextAlign,
VerticalAlign, NumberFormat, FilterOperator, FilterCondition, FilterState,
HiddenState, AggregateFunction, PivotFieldSort, PivotField, PivotValueField,
PivotFilterField, PivotTableDefinition, PivotRecord, GroupNode, PivotCellType,
PivotCell, PivotResult, Ref, Sref, Range, MergeSpan, Direction, Axis,
SelectionType, GridResolver, Store, LayoutRect, Theme, RangeStylePatch,
LocaleFormatPreview, FormatValueOptions, InferInputOptions, InferredInput,
InferredInputFormat, ChartType, SheetChart, WorksheetFilterState, Worksheet,
TabType, SheetKind, TabMeta, SpreadsheetDocument) and keep a separate runtime
export { ... } for actual values (e.g. MemStore, ReadOnlyStore,
findEdgeWithIndex, Grid, Cell classes/constructors or functions) so that only
type-only imports use export type and runtime symbols remain in the normal
export list.

---

Nitpick comments:
In `@docs/tasks/active/20260313-backend-dev-start-failure-todo.md`:
- Around line 67-70: The second verification step uses a bare "tsc --build
tsconfig.build.json --force --listEmittedFiles" which is ambiguous; update that
line to either use the same pnpm wrapper (pnpm --filter `@wafflebase/backend` exec
tsc --build tsconfig.build.json --force --listEmittedFiles) or explicitly state
"run from packages/backend" so the commands are deterministic and context-stable
in the repro steps.

In
`@docs/tasks/active/20260313-crdt-structural-concurrency-retrospective-todo.md`:
- Around line 3-4: Replace non-hyphenated compound modifiers with hyphenated
forms for clarity: change occurrences of "stable grid based" to
"stable-grid-based" and "visual index based" to "visual-index-based" throughout
the document (including the phrases in the current section and the lines
referenced as 36-39); update any variants like "stable-grid based" or
"visual-index based" to the fully hyphenated form to ensure consistent, readable
technical prose.

In `@docs/tasks/active/20260314-yorkie-worksheet-shape-migration-lessons.md`:
- Line 6: The phrase "side-effect free" should be hyphenated as the compound
adjective "side-effect-free"; update the text in the document where the label
sentence contains "side-effect free" so it reads "side-effect-free" to clarify
the compound modifier.

In `@packages/backend/scripts/copy-yorkie-documents.js`:
- Around line 104-126: The function fetchExistingYorkieKeys currently calls
mongosh via execFileSync and will throw a confusing error if mongosh isn't
installed; add a pre-flight check before invoking execFileSync (e.g., call
execFileSync('mongosh', ['--version']) or spawnSync to verify availability and a
zero exit code) and replace the raw exception with a clear, user-facing error
that explains the missing mongosh dependency and points to installation
instructions; update any usage/help text in the same file to document the
mongosh requirement and refer to the new check so users see an actionable
message instead of the low-level exception.

In `@packages/backend/scripts/copy-yorkie-documents.ts`:
- Around line 370-373: The loop over existingKeys currently awaits
copyDocument(sourceClient, targetClient, key) directly so a thrown error aborts
the whole process; wrap the per-key call in a try/catch inside the for loop (the
loop that iterates existingKeys and calls copyDocument) to catch and log errors
for that key (include the key and error message), push failed keys/errors into a
local failures array, continue processing remaining keys, and after the loop
report a summary (e.g., log or throw a single aggregated error) if failures is
non-empty so callers can see which documents failed.
- Line 362: The filter that builds missingKeys uses Array.includes on
existingKeys which is O(n) per check; change to create a Set from existingKeys
(e.g., const existingKeySet = new Set(existingKeys)) and then use
existingKeySet.has(key) inside the candidateKeys.filter callback to get O(1)
membership checks and improve performance (update the reference in the filter
that currently uses existingKeys.includes).

In `@packages/backend/scripts/migrate-yorkie-worksheet-shape.d.ts`:
- Line 1: The file migrate-yorkie-worksheet-shape.d.ts currently only contains
an empty "export {}" which is unclear; either delete this declaration file if it
serves no purpose, or replace it with a short explanatory comment and a clearer
intent (e.g., explain it exists to force module scope / prevent accidental
imports or to satisfy TypeScript module resolution), or change it to a proper
declaration (declare module '...' { /* types */ }) if it is intended to provide
typings; update the file migrate-yorkie-worksheet-shape.d.ts accordingly and
ensure the change matches the intended use of the CLI script or module.

In `@packages/backend/src/yorkie/worksheet-shape-migration.spec.ts`:
- Around line 252-258: Add extra negative test cases for
migrateYorkieWorksheetShape to cover more unsupported shapes: include at least
one test that passes an array root (e.g., []), one that passes a primitive root
(e.g., 'string' cast as never), and optionally a nested unexpected structure,
each asserting they throw 'Unsupported Yorkie spreadsheet document shape' (or at
least throw); update the spec around the existing it('throws for unsupported
shapes') block to add these new it(...) cases referencing
migrateYorkieWorksheetShape so the function's validation covers arrays,
primitives, and nested invalid inputs.

In `@packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts`:
- Around line 72-93: Add clear documentation that the parameter index is 1-based
and the function converts it to 0-based via Math.max(0, index - 1); update the
function comment or JSDoc for insertYorkieWorksheetAxis to state the 1-based
indexing convention (for both row and col axes) and note that ensureAxisLength
and the splice call expect the converted 0-based index, so callers should pass
1-based positions (use index = 1 for the first position).

In `@packages/frontend/tests/app/spreadsheet/chart-utils.test.ts`:
- Around line 45-63: The buildDoc helper manually constructs the spreadsheet
envelope; replace that with the shared factory createSpreadsheetDocument to keep
test fixtures aligned with canonical defaults—create the worksheet via
createWorksheet(), write cells with writeWorksheetCell(parseRef(...)), then call
createSpreadsheetDocument to produce the full document envelope and attach the
worksheet (or pass it into the factory if supported); update buildDoc to return
the result of createSpreadsheetDocument (or the factory result after assigning
tabs, tabOrder, and sheets) instead of hand-assembling the object so you avoid
duplicating schema defaults.

In `@packages/frontend/tests/helpers/two-user-yorkie.ts`:
- Around line 63-86: The switch in applyStoreOp handling ConcurrencyOp kinds is
non-exhaustive; add a default branch that fails fast for unknown kinds (so new
variants don't silently no-op). Update applyStoreOp to include a default case
that asserts exhaustiveness (e.g., cast op to never or throw an Error) and
surface the unexpected op.kind; reference applyStoreOp, ConcurrencyOp, parseRef,
YorkieStore.set and YorkieStore.shiftCells to locate where to add the
default/exhaustive check.
- Around line 46-48: The helper createStore currently silences type checking by
casting doc to never; update it to preserve type-safety by making createStore
generic and casting via unknown (or typing the parameter to the YorkieStore
constructor type). For example, change function createStore(doc: T) to a generic
signature (e.g., function createStore<T>(doc: T): YorkieStore and cast doc as
unknown as ConstructorParamType) so the YorkieStore instantiation (new
YorkieStore(...)) no longer uses "as never" and mismatched shapes are caught by
the compiler; locate the createStore function and the YorkieStore constructor
usage to apply this change.

In `@packages/sheet/src/model/workbook/worksheet-grid.ts`:
- Around line 88-89: The linter flags useIterableCallbackReturn because
rowOrder.forEach and colOrder.forEach callbacks return Map.set(); replace both
forEach uses with explicit for...of loops to iterate arrays and call
rowIndex.set(rowId, index + 1) and colIndex.set(colId, index + 1) inside the
loop bodies (use an index counter or Array.prototype.entries() in the for...of)
so that rowOrder, colOrder, rowIndex.set, and colIndex.set remain the same but
no callback return value is produced.

In `@packages/sheet/src/model/workbook/worksheet-record.ts`:
- Around line 71-81: The function parseWorksheetCellKey silently returns empty
strings when WorksheetCellKeySeparator isn't found which can hide malformed
input; update parseWorksheetCellKey to fail fast by throwing a descriptive Error
(include the invalid key and the expected WorksheetCellKeySeparator) instead of
returning {rowId: '', colId: ''}, and update any callers or types to handle the
thrown error (or alternatively change the return type to nullable and return
null consistently if you prefer non-throwing behavior); locate
parseWorksheetCellKey and replace the fallback branch with a clear error throw
(or null return) and ensure tests/call sites handle the new behavior.

In `@packages/sheet/src/view/formulabar.ts`:
- Line 2: Replace the runtime import of Sheet with a type-only import: change
"import { Sheet } from '../model/worksheet/sheet';" to "import type { Sheet }
from '../model/worksheet/sheet';" in formulabar.ts, ensuring that all references
to Sheet (e.g., the type annotations used in the form bar functions/variables)
remain type-only and that no runtime usage of Sheet exists in the file so the
compiler emits no runtime import.

In `@packages/sheet/src/view/spreadsheet.ts`:
- Around line 1-12: The import from ../model/core/types should be changed to a
type-only import because all symbols (Axis, BorderPreset, CellStyle,
ConditionalFormatRule, FilterCondition, GridResolver, Range, Ref, SelectionType)
are used only for typing; update the import line in spreadsheet.ts to use an
"import type { ... } from '../model/core/types';" form so the core/worksheet
boundary is preserved and runtime bundling won't include these types.

In `@packages/sheet/test/helpers/concurrency-driver.ts`:
- Around line 24-45: The switch in applyConcurrencyOp (function) covers current
ConcurrencyOp kinds but lacks an exhaustive default; add an explicit
exhaustiveness check so new ConcurrencyOp variants fail fast: either add a
default branch that throws a descriptive Error (e.g., `throw new
Error('Unhandled ConcurrencyOp kind: ' + (op as any).kind)`) or
introduce/consume a utility like assertNever(value: never) and call it in the
default case to ensure the compiler enforces exhaustiveness for ConcurrencyOp
and surface unhandled kinds at runtime/compile time.
- Around line 89-94: The snapshotMatchesOneOf function currently compares
snapshots with JSON.stringify which can fail if object key order differs; update
snapshotMatchesOneOf (working with ConcurrencySnapshot) to perform a stable
deep-equality comparison instead of raw stringify — either by normalizing
objects with a deterministic JSON.stringify (use a normalize helper that sorts
Object.keys(obj) in the replacer) before comparing, or call a reliable deepEqual
(e.g. lodash/isEqual) to compare actual against each candidate; ensure the
helper handles nested objects consistently and use that in the candidates.some
check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bed6be69-5b53-4bf5-b30b-30b511d39f0a

📥 Commits

Reviewing files that changed from the base of the PR and between 61e7e3c and e090cb4.

⛔ Files ignored due to path filters (3)
  • packages/backend/scripts/copy-yorkie-documents.js.map is excluded by !**/*.map
  • packages/backend/scripts/migrate-yorkie-worksheet-shape.js.map is excluded by !**/*.map
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (135)
  • docs/design/README.md
  • docs/design/datasource.md
  • docs/design/frontend.md
  • docs/design/spreadsheet-collaboration.md
  • docs/tasks/README.md
  • docs/tasks/active/20260313-backend-dev-start-failure-lessons.md
  • docs/tasks/active/20260313-backend-dev-start-failure-todo.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-analysis-lessons.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-analysis-todo.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-fix-lessons.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-fix-todo.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-retrospective-lessons.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-retrospective-todo.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-test-architecture-lessons.md
  • docs/tasks/active/20260313-crdt-structural-concurrency-test-architecture-todo.md
  • docs/tasks/active/20260314-collaboration-design-docs-lessons.md
  • docs/tasks/active/20260314-collaboration-design-docs-todo.md
  • docs/tasks/active/20260314-local-hackerwins-access-lessons.md
  • docs/tasks/active/20260314-local-hackerwins-access-todo.md
  • docs/tasks/active/20260314-local-prod-restore-lessons.md
  • docs/tasks/active/20260314-local-prod-restore-todo.md
  • docs/tasks/active/20260314-worksheet-model-boundary-lessons.md
  • docs/tasks/active/20260314-worksheet-model-boundary-todo.md
  • docs/tasks/active/20260314-yorkie-worksheet-shape-migration-lessons.md
  • docs/tasks/active/20260314-yorkie-worksheet-shape-migration-todo.md
  • package.json
  • packages/backend/package.json
  • packages/backend/scripts/copy-yorkie-documents.d.ts
  • packages/backend/scripts/copy-yorkie-documents.js
  • packages/backend/scripts/copy-yorkie-documents.ts
  • packages/backend/scripts/migrate-yorkie-worksheet-shape.d.ts
  • packages/backend/scripts/migrate-yorkie-worksheet-shape.js
  • packages/backend/scripts/migrate-yorkie-worksheet-shape.ts
  • packages/backend/src/api/v1/cells.controller.ts
  • packages/backend/src/yorkie/worksheet-shape-migration.spec.ts
  • packages/backend/src/yorkie/worksheet-shape-migration.ts
  • packages/backend/src/yorkie/yorkie.types.ts
  • packages/backend/tsconfig.build.json
  • packages/backend/tsconfig.json
  • packages/frontend/package.json
  • packages/frontend/src/app/documents/document-detail.tsx
  • packages/frontend/src/app/documents/migration.ts
  • packages/frontend/src/app/spreadsheet/chart-utils.ts
  • packages/frontend/src/app/spreadsheet/pivot/use-pivot-table.ts
  • packages/frontend/src/app/spreadsheet/sheet-view.tsx
  • packages/frontend/src/app/spreadsheet/yorkie-store.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-structure.ts
  • packages/frontend/src/types/users.ts
  • packages/frontend/src/types/worksheet.ts
  • packages/frontend/tests/app/documents/migration.test.ts
  • packages/frontend/tests/app/spreadsheet/chart-utils.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-concurrency-repro.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-concurrency.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-worksheet-structure.test.ts
  • packages/frontend/tests/helpers/two-user-yorkie.ts
  • packages/frontend/tests/resolve-hooks.mjs
  • packages/sheet/package.json
  • packages/sheet/src/formula/arguments.ts
  • packages/sheet/src/formula/formula.ts
  • packages/sheet/src/formula/functions.ts
  • packages/sheet/src/index.ts
  • packages/sheet/src/model/core/coordinates.ts
  • packages/sheet/src/model/core/locale.ts
  • packages/sheet/src/model/core/types.ts
  • packages/sheet/src/model/pivot/aggregate.ts
  • packages/sheet/src/model/pivot/calculate.ts
  • packages/sheet/src/model/pivot/group.ts
  • packages/sheet/src/model/pivot/materialize.ts
  • packages/sheet/src/model/pivot/parse.ts
  • packages/sheet/src/model/workbook/worksheet-document.ts
  • packages/sheet/src/model/workbook/worksheet-grid.ts
  • packages/sheet/src/model/workbook/worksheet-record.ts
  • packages/sheet/src/model/worksheet/calculator.ts
  • packages/sheet/src/model/worksheet/conditional-format.ts
  • packages/sheet/src/model/worksheet/dimensions.ts
  • packages/sheet/src/model/worksheet/format.ts
  • packages/sheet/src/model/worksheet/grids.ts
  • packages/sheet/src/model/worksheet/input.ts
  • packages/sheet/src/model/worksheet/merging.ts
  • packages/sheet/src/model/worksheet/range-styles.ts
  • packages/sheet/src/model/worksheet/sheet.ts
  • packages/sheet/src/model/worksheet/shifting.ts
  • packages/sheet/src/store/cell-index.ts
  • packages/sheet/src/store/find-edge.ts
  • packages/sheet/src/store/memory.ts
  • packages/sheet/src/store/readonly.ts
  • packages/sheet/src/store/store.ts
  • packages/sheet/src/view/cellinput.ts
  • packages/sheet/src/view/formulabar.ts
  • packages/sheet/src/view/gridcanvas.ts
  • packages/sheet/src/view/layout.ts
  • packages/sheet/src/view/overlay.ts
  • packages/sheet/src/view/spreadsheet.ts
  • packages/sheet/src/view/worksheet.ts
  • packages/sheet/test/formula/cross-sheet-ref.test.ts
  • packages/sheet/test/formula/formula.test.ts
  • packages/sheet/test/helpers/concurrency-case-table.ts
  • packages/sheet/test/helpers/concurrency-driver.ts
  • packages/sheet/test/model/conditional-format.test.ts
  • packages/sheet/test/model/format.test.ts
  • packages/sheet/test/model/grids.test.ts
  • packages/sheet/test/model/input.test.ts
  • packages/sheet/test/model/range-styles.test.ts
  • packages/sheet/test/model/redirect-formula.test.ts
  • packages/sheet/test/sheet/autofill.test.ts
  • packages/sheet/test/sheet/calculation.test.ts
  • packages/sheet/test/sheet/clipboard.test.ts
  • packages/sheet/test/sheet/concurrency-matrix.test.ts
  • packages/sheet/test/sheet/coordinates.test.ts
  • packages/sheet/test/sheet/cross-sheet-calculation.test.ts
  • packages/sheet/test/sheet/filter.test.ts
  • packages/sheet/test/sheet/find.test.ts
  • packages/sheet/test/sheet/formatting.test.ts
  • packages/sheet/test/sheet/freeze-pane.test.ts
  • packages/sheet/test/sheet/hidden.test.ts
  • packages/sheet/test/sheet/input-inference.test.ts
  • packages/sheet/test/sheet/manipulation.test.ts
  • packages/sheet/test/sheet/merge.test.ts
  • packages/sheet/test/sheet/moving.test.ts
  • packages/sheet/test/sheet/pivot-aggregate.test.ts
  • packages/sheet/test/sheet/pivot-calculate.test.ts
  • packages/sheet/test/sheet/pivot-e2e.test.ts
  • packages/sheet/test/sheet/pivot-group.test.ts
  • packages/sheet/test/sheet/pivot-materialize.test.ts
  • packages/sheet/test/sheet/pivot-parse.test.ts
  • packages/sheet/test/sheet/pivot-protection.test.ts
  • packages/sheet/test/sheet/sheet.test.ts
  • packages/sheet/test/sheet/shifting.test.ts
  • packages/sheet/test/store/find-edge.test.ts
  • packages/sheet/test/store/store.test.ts
  • packages/sheet/test/view/dimensions.test.ts
  • packages/sheet/test/view/gridcanvas.test.ts
  • packages/sheet/test/view/overlay.test.ts
  • scripts/test/verify-entropy.test.mjs
💤 Files with no reviewable changes (2)
  • packages/frontend/tests/app/documents/migration.test.ts
  • packages/frontend/src/app/documents/migration.ts


### Verification

- `PGPASSWORD=wafflebase psql -h localhost -p 5432 -U wafflebase -d wafflebase_prod_restore -c "select id, username, email from \"User\" order by id asc;"`

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

Avoid unnecessary PII access in verification query.

Line 45 selects email, but the workflow only needs ownership/workspace verification. Please remove email from the query to minimize exposure when working with restored production-like data.

🔐 Proposed minimal-change fix
-- `PGPASSWORD=wafflebase psql -h localhost -p 5432 -U wafflebase -d wafflebase_prod_restore -c "select id, username, email from \"User\" order by id asc;"`
+- `PGPASSWORD=wafflebase psql -h localhost -p 5432 -U wafflebase -d wafflebase_prod_restore -c "select id, username from \"User\" order by id asc;"`
📝 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
- `PGPASSWORD=wafflebase psql -h localhost -p 5432 -U wafflebase -d wafflebase_prod_restore -c "select id, username, email from \"User\" order by id asc;"`
- `PGPASSWORD=wafflebase psql -h localhost -p 5432 -U wafflebase -d wafflebase_prod_restore -c "select id, username from \"User\" order by id asc;"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tasks/active/20260314-local-hackerwins-access-todo.md` at line 45, The
verification query currently selects PII (email); update the SQL in the
documented command so it only returns non-PII fields used for
ownership/workspace verification (select id, username from "User" order by id
asc) — locate the command string containing `select id, username, email from
"User" order by id asc;` and remove `email` from the SELECT list, keeping
quoting and the rest of the psql invocation unchanged.

Comment on lines +77 to +82
- `pnpm --filter @wafflebase/backend exec tsx scripts/copy-yorkie-documents.ts --database-url postgresql://wafflebase:wafflebase@localhost:5432/wafflebase_prod_restore --mongo-url mongodb://127.0.0.1:37017/yorkie-meta --project-public-key fbuqYRxotajGGzb3kUD6aX --source-rpc-addr http://localhost:38080 --source-api-key fbuqYRxotajGGzb3kUD6aX --target-rpc-addr http://localhost:8080`
- `DATABASE_URL=postgresql://wafflebase:wafflebase@localhost:5432/wafflebase_prod_restore YORKIE_RPC_ADDR=http://localhost:8080 pnpm --filter @wafflebase/backend migrate:yorkie:worksheet-shape --document 4ca1ed51-1443-4dee-8575-f39035ad9eec`
- `pnpm --filter @wafflebase/backend test -- worksheet-shape-migration.spec.ts`
- `pnpm --filter @wafflebase/backend exec tsx scripts/copy-yorkie-documents.ts --database-url postgresql://wafflebase:wafflebase@localhost:5432/wafflebase_prod_restore --mongo-url mongodb://127.0.0.1:37017/yorkie-meta --project-public-key fbuqYRxotajGGzb3kUD6aX --source-rpc-addr http://localhost:38080 --source-api-key fbuqYRxotajGGzb3kUD6aX --target-rpc-addr http://localhost:8080 --document 3f54103b-9ba0-446b-a13b-96331d6a4199`
- `DATABASE_URL=postgresql://wafflebase:wafflebase@localhost:5432/wafflebase_prod_restore YORKIE_RPC_ADDR=http://localhost:8080 pnpm --filter @wafflebase/backend migrate:yorkie:worksheet-shape --document 3f54103b-9ba0-446b-a13b-96331d6a4199`
- `DATABASE_URL=postgresql://wafflebase:wafflebase@localhost:5432/wafflebase_prod_restore YORKIE_RPC_ADDR=http://localhost:8080 pnpm --filter @wafflebase/backend migrate:yorkie:worksheet-shape --all`

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

🧩 Analysis chain

🏁 Script executed:

cat -n docs/tasks/active/20260314-local-prod-restore-todo.md | sed -n '70,90p'

Repository: wafflebase/wafflebase

Length of output: 2010


🏁 Script executed:

# Check if these specific key values appear elsewhere in the codebase
rg "fbuqYRxotajGGzb3kUD6aX" --type md --type ts --type js

Repository: wafflebase/wafflebase

Length of output: 988


🏁 Script executed:

# Check the full context of the file to understand its purpose
head -20 docs/tasks/active/20260314-local-prod-restore-todo.md

Repository: wafflebase/wafflebase

Length of output: 701


🏁 Script executed:

# Search for these key values in test files and config
rg "fbuqYRxotajGGzb3kUD6aX|yorkie.*key|project.*public.*key" --type ts --type js -i | head -30

Repository: wafflebase/wafflebase

Length of output: 3113


🏁 Script executed:

# Check if these look like dummy/test credentials by searching for similar patterns
rg "project-public-key|source-api-key" --type ts --type js -A 2 -B 2 | head -40

Repository: wafflebase/wafflebase

Length of output: 2927


🏁 Script executed:

# Check for any .env.example or test configuration files
fd -e ".env.example" -e ".env.test" -e ".env.local"

Repository: wafflebase/wafflebase

Length of output: 47


Use environment variables instead of embedding concrete credential values.

Lines 77 and 80 include credential parameters (--project-public-key fbuqYRxotajGGzb3kUD6aX, --source-api-key fbuqYRxotajGGzb3kUD6aX) and plaintext database credentials. Even though these appear to be test values, documentation should use environment variable references to maintain reusability and follow credential handling best practices.

Replace concrete values with environment variable references:

  • --project-public-key "$YORKIE_PROJECT_PUBLIC_KEY"
  • --source-api-key "$SOURCE_YORKIE_API_KEY"
  • postgresql://$DB_USER:$DB_PASSWORD@localhost:5432/... (for database URLs)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tasks/active/20260314-local-prod-restore-todo.md` around lines 77 - 82,
The docs embed concrete credentials in commands that call the
copy-yorkie-documents.ts script and DATABASE_URL; update the examples to use
environment variable placeholders instead of literals — replace the
--project-public-key and --source-api-key flags passed to
scripts/copy-yorkie-documents.ts with --project-public-key
"$YORKIE_PROJECT_PUBLIC_KEY" and --source-api-key "$SOURCE_YORKIE_API_KEY", and
replace hard-coded DB credentials in DATABASE_URL values with
postgresql://$DB_USER:$DB_PASSWORD@localhost:5432/<DB_NAME> (also keep
YORKIE_RPC_ADDR and any other addresses as variables where appropriate).

Comment on lines +164 to +166
const targetClient = new sdk_1.default.Client({
rpcAddr: options.targetRpcAddr,
});

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's a target-api-key argument handling in the source file
rg -n "target-api-key|targetApiKey" packages/backend/scripts/

Repository: wafflebase/wafflebase

Length of output: 47


🏁 Script executed:

cat -n packages/backend/scripts/copy-yorkie-documents.js | head -200

Repository: wafflebase/wafflebase

Length of output: 8327


Document the assumption that the target Yorkie server does not require authentication.

The target client is intentionally configured without an apiKey (no --target-api-key argument is parsed). This design assumes the target server does not require authentication. If this assumption changes or if target-server authentication becomes needed, the script will fail. Consider adding a comment in the code explaining this design decision.

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

In `@packages/backend/scripts/copy-yorkie-documents.js` around lines 164 - 166,
Add a short inline comment above the target client construction (the
sdk_1.default.Client instantiation creating targetClient using
options.targetRpcAddr) stating that the target Yorkie server is assumed to not
require authentication and that no --target-api-key is parsed; note that if
target-server authentication is later required the script will need to accept
and pass an apiKey to the Client. Keep the comment concise and reference the
absence of apiKey handling so future maintainers know why no credential is
provided.

Comment thread packages/backend/scripts/migrate-yorkie-worksheet-shape.js
Comment on lines +126 to +137
const summary = {
processed: 0,
changed: 0,
unchanged: 0,
byKind: {
current: 0,
'initialized-empty': 0,
'legacy-flat': 0,
'legacy-tabbed': 0,
},
failures: [],
};

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

Summary byKind initialization also missing current-flat.

The summary.byKind object initialization in the .js file is missing the 'current-flat': 0 entry that exists in the TypeScript source at line 347. This would cause summary.byKind['current-flat'] to be undefined, leading to incorrect summary output.

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

In `@packages/backend/scripts/migrate-yorkie-worksheet-shape.js` around lines 126
- 137, The summary.byKind object is missing the 'current-flat' key so
summary.byKind['current-flat'] will be undefined; update the initialization of
the summary constant (the summary object with processed/changed/unchanged and
byKind) to include a "'current-flat': 0" entry alongside current,
'initialized-empty', 'legacy-flat', and 'legacy-tabbed' so counts for
current-flat are tracked correctly.

Comment thread packages/backend/src/yorkie/worksheet-shape-migration.ts
Comment on lines 7 to +16
"type": "module",
"main": "src/index.js",
"main": "dist/wafflebase-sheet.js",
"module": "dist/wafflebase-sheet.es.js",
"types": "dist/wafflebase-sheet.es.d.ts",
"exports": {
".": {
"types": "./dist/wafflebase-sheet.es.d.ts",
"import": "./dist/wafflebase-sheet.es.js",
"require": "./dist/wafflebase-sheet.js",
"default": "./dist/wafflebase-sheet.es.js"

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Inspect sheet package build output naming/format config"
fd -a "vite.build.ts" packages/sheet | xargs -I{} sh -c 'echo "--- {}"; sed -n "1,220p" "{}"'

echo
echo "2) Inspect generated dist files (if present) to see cjs/esm artifact names"
fd -a "wafflebase-sheet*" packages/sheet/dist || true

echo
echo "3) Find CommonJS require callsites for `@wafflebase/sheet`"
rg -nP --type=ts --type=js --type=tsx --type=jsx -C2 'require\s*\(\s*["'\'']@wafflebase/sheet["'\'']\s*\)'

echo
echo "4) Inspect package.json export map currently in use"
sed -n '1,140p' packages/sheet/package.json

Repository: wafflebase/wafflebase

Length of output: 856


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Full vite.build.ts for build config ==="
cat packages/sheet/vite.build.ts

echo
echo "=== 2) Full package.json exports section ==="
jq '.exports' packages/sheet/package.json

echo
echo "=== 3) Search for require calls (fixed syntax) ==="
rg 'require.*@wafflebase/sheet' -A 2 -B 2 --no-heading || echo "No require() calls found"

echo
echo "=== 4) Check package.json main/module/types fields ==="
jq '{main, module, types, type}' packages/sheet/package.json

echo
echo "=== 5) Check if vite produces cjs format ==="
grep -A 30 "build:" packages/sheet/vite.build.ts | grep -E "(lib|format|cjs)" || echo "No explicit cjs config found"

Repository: wafflebase/wafflebase

Length of output: 1075


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Vite lib mode formats (checking Vite docs/defaults) ==="
# Check package.json dependencies to see Vite version
jq '.devDependencies | select(.vite)' packages/sheet/package.json

echo
echo "=== Check if there's a dist build or build logs ==="
find packages/sheet -name "*.cjs.js" -o -name "*.umd.js" 2>/dev/null | head -20 || echo "No dist artifacts found"

echo
echo "=== Check package.json main/module/types directly ==="
cat packages/sheet/package.json | head -20

echo
echo "=== Look for build scripts that show format intention ==="
jq '.scripts' packages/sheet/package.json || echo "No scripts section"

echo
echo "=== Verify: Does Vite lib mode include cjs by default? Check tsconfig or build docs ==="
ls -la packages/sheet/tsconfig* packages/sheet/vite* 2>/dev/null | grep -v node_modules || true

Repository: wafflebase/wafflebase

Length of output: 1776


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check for imports of `@wafflebase/sheet` across monorepo ==="
rg '@wafflebase/sheet' packages/ --type js --type ts --type json -l

echo
echo "=== Check if any of those imports use require() ==="
rg 'require.*@wafflebase/sheet|import.*@wafflebase/sheet' packages/ --type js --type ts -B 2 -A 2 | head -30

echo
echo "=== Verify Vite lib formats generated (check Vite 6 defaults) ==="
# Vite 6 lib mode defaults to building es, cjs, umd when name is set
echo "Vite 6.4.1 lib mode: When 'name' is set (wafflebase-sheet), default formats are: es, cjs, umd"
echo "The custom fileName maps them as shown in vite.build.ts"

Repository: wafflebase/wafflebase

Length of output: 4335


exports.require should map to the CJS artifact, not UMD.

Line 15 maps "require" to ./dist/wafflebase-sheet.js (UMD format), but should point to ./dist/wafflebase-sheet.cjs.js (the actual CommonJS artifact from Vite's lib mode build). While UMD is technically compatible in both contexts, this violates idiomatic practice for "type": "module" packages. Update the exports map to use the explicit CJS file:

"require": "./dist/wafflebase-sheet.cjs.js"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/package.json` around lines 7 - 16, The package exports map
currently points "require" at the UMD bundle file (./dist/wafflebase-sheet.js);
change the exports entry for "require" to reference the actual CommonJS artifact
produced by the build (./dist/wafflebase-sheet.cjs.js) so the package with
"type": "module" exposes an explicit CJS file for require consumers—update the
"." exports object's "require" property accordingly (search for the exports
block and the "require" key in package.json).

Comment thread packages/sheet/src/model/workbook/worksheet-document.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.

🧹 Nitpick comments (2)
docs/tasks/active/20260314-ci-export-failure-todo.md (1)

1-4: Consider linking to the lessons learned document.

To improve navigation between related documentation, consider adding a reference to the lessons learned file where this investigation's key takeaway is captured.

📝 Suggested enhancement with cross-reference
 # CI Export Failure Investigation
 
 Investigate and fix the GitHub Actions failure caused by invalid runtime ESM
 exports from the `@wafflebase/sheet` barrel.
+
+> **Lesson captured:** [Barrel Files Must Separate Type-Only Exports From Runtime Exports](./20260314-ci-export-failure-lessons.md)
 
 ## Tasks
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tasks/active/20260314-ci-export-failure-todo.md` around lines 1 - 4, Add
a cross-reference from this task doc ("CI Export Failure Investigation" in
20260314-ci-export-failure-todo.md) to the lessons-learned document: insert a
short sentence or markdown link after "Consider linking to the lessons learned
document." that points to the canonical lessons file (e.g.,
"docs/lessons/ci-export-failure-lessons.md" or whatever the repository's lessons
file is named), include link text like "See lessons learned" and ensure the link
is relative and uses standard Markdown so readers can navigate between the task
and the lessons-learned document.
docs/tasks/active/20260314-ci-export-failure-lessons.md (1)

1-9: Consider adding a cross-reference to the investigation workflow.

To improve discoverability, consider adding a reference to the related investigation workflow document at docs/tasks/active/20260314-ci-export-failure-todo.md. This would help readers find the full context and verification steps.

📝 Suggested enhancement with cross-reference
 # CI Export Failure Investigation — Lessons
+
+> See also: [CI Export Failure Investigation workflow](./20260314-ci-export-failure-todo.md)
 
 ## Barrel Files Must Separate Type-Only Exports From Runtime Exports
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tasks/active/20260314-ci-export-failure-lessons.md` around lines 1 - 9,
Add a short cross-reference sentence and link to the related investigation
workflow document near the top of this note (under the "Barrel Files Must
Separate Type-Only Exports From Runtime Exports" heading) that points readers to
the CI export failure investigation/workflow (the "todo" workflow) so they can
follow the full investigation and verification steps; keep the wording concise
(one or two sentences), include the document title as link text, and place it
where readers scanning the lessons will notice it immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/tasks/active/20260314-ci-export-failure-lessons.md`:
- Around line 1-9: Add a short cross-reference sentence and link to the related
investigation workflow document near the top of this note (under the "Barrel
Files Must Separate Type-Only Exports From Runtime Exports" heading) that points
readers to the CI export failure investigation/workflow (the "todo" workflow) so
they can follow the full investigation and verification steps; keep the wording
concise (one or two sentences), include the document title as link text, and
place it where readers scanning the lessons will notice it immediately.

In `@docs/tasks/active/20260314-ci-export-failure-todo.md`:
- Around line 1-4: Add a cross-reference from this task doc ("CI Export Failure
Investigation" in 20260314-ci-export-failure-todo.md) to the lessons-learned
document: insert a short sentence or markdown link after "Consider linking to
the lessons learned document." that points to the canonical lessons file (e.g.,
"docs/lessons/ci-export-failure-lessons.md" or whatever the repository's lessons
file is named), include link text like "See lessons learned" and ensure the link
is relative and uses standard Markdown so readers can navigate between the task
and the lessons-learned document.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52fe1417-c6fb-484d-879d-9ecb5b925515

📥 Commits

Reviewing files that changed from the base of the PR and between e090cb4 and 05e44c9.

📒 Files selected for processing (4)
  • docs/tasks/README.md
  • docs/tasks/active/20260314-ci-export-failure-lessons.md
  • docs/tasks/active/20260314-ci-export-failure-todo.md
  • packages/sheet/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/tasks/README.md

@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.

🧹 Nitpick comments (3)
packages/sheet/test/helpers/concurrency-driver.ts (1)

93-98: JSON.stringify comparison is acceptable but has a subtle ordering assumption.

Since ConcurrencySnapshot.cells is a Record<Sref, ...>, JSON.stringify will serialize keys in insertion order. This works correctly as long as both actual and candidate snapshots are built with the same key ordering (e.g., both using the same refs array via captureConcurrencySnapshot).

If manually constructed candidate snapshots in test cases have different key ordering, comparison could fail unexpectedly. For test helpers this is likely fine, but for extra robustness you could normalize key order before comparison.

🔧 Optional: Normalize key order for comparison
 export function snapshotMatchesOneOf(
   actual: ConcurrencySnapshot,
   candidates: ConcurrencySnapshot[],
 ): boolean {
-  return candidates.some((candidate) => JSON.stringify(candidate) === JSON.stringify(actual));
+  const normalize = (snap: ConcurrencySnapshot): string => {
+    const sortedCells = Object.keys(snap.cells)
+      .sort()
+      .reduce((acc, key) => ({ ...acc, [key]: snap.cells[key] }), {});
+    return JSON.stringify({ cells: sortedCells });
+  };
+  const actualStr = normalize(actual);
+  return candidates.some((candidate) => normalize(candidate) === actualStr);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheet/test/helpers/concurrency-driver.ts` around lines 93 - 98, The
JSON.stringify comparison in snapshotMatchesOneOf can fail if Record keys are in
different insertion orders; update snapshotMatchesOneOf (and/or add a small
helper like normalizeSnapshot) to produce a deterministic representation by
sorting the keys of ConcurrencySnapshot.cells (e.g., iterate
Object.keys(snapshot.cells).sort() and build a new object with those ordered
keys, and likewise normalize any nested objects/arrays if needed) and then
compare JSON.stringify(normalizeSnapshot(candidate)) ===
JSON.stringify(normalizeSnapshot(actual)); this preserves existing behavior but
removes the ordering sensitivity when comparing ConcurrencySnapshot instances.
docs/design/spreadsheet-collaboration.md (1)

231-232: Minor grammar fix: hyphenate "top-level".

Per static analysis, "top level" should be hyphenated when used as a compound modifier.

📝 Suggested fix
-  - top level `tabs`, `tabOrder`, `sheets`
+  - top-level `tabs`, `tabOrder`, `sheets`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/spreadsheet-collaboration.md` around lines 231 - 232, Change the
phrase "top level `tabs`, `tabOrder`, `sheets`" to use a hyphenated compound
modifier: "top-level `tabs`, `tabOrder`, `sheets`" so the document consistently
hyphenates "top-level" when used as a compound adjective (the worksheet-local
items `cells`, `rowOrder`, `colOrder`, `nextRowId`, `nextColId` remain
unchanged).
packages/sheet/src/index.ts (1)

127-141: Expose a callable alias for the default-document factory.

initialSpreadsheetDocument now reads like shared data, but this symbol now creates fresh state. Exporting a createInitialSpreadsheetDocument alias alongside the legacy name would make call sites much harder to misuse.

♻️ Suggested export shape
 import {
   DEFAULT_TAB_ID,
   DEFAULT_TAB_NAME,
   type ChartType,
   type SheetChart,
   type WorksheetFilterState,
   type Worksheet,
   type TabType,
   type SheetKind,
   type TabMeta,
   type SpreadsheetDocument,
   createWorksheet,
   createSpreadsheetDocument,
-  initialSpreadsheetDocument,
+  initialSpreadsheetDocument as createInitialSpreadsheetDocument,
 } from './model/workbook/worksheet-document';

 ...

   DEFAULT_TAB_ID,
   DEFAULT_TAB_NAME,
   createWorksheet,
   createSpreadsheetDocument,
-  initialSpreadsheetDocument,
+  createInitialSpreadsheetDocument,
+  createInitialSpreadsheetDocument as initialSpreadsheetDocument,
 };

As per coding guidelines, "Use descriptive variable and function names that clearly convey intent and purpose".

Also applies to: 212-216

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

In `@packages/sheet/src/index.ts` around lines 127 - 141, Export a clearly named
factory alias for the default-document creator: add and export
createInitialSpreadsheetDocument as a callable alias that forwards to the
existing initialSpreadsheetDocument factory so call sites use a verb-like name
instead of a value-like name; update any other "initial..." exports in this
module (the ones alongside createWorksheet and createSpreadsheetDocument) to
provide matching createX aliases as well so factories are consistently named.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/design/spreadsheet-collaboration.md`:
- Around line 231-232: Change the phrase "top level `tabs`, `tabOrder`,
`sheets`" to use a hyphenated compound modifier: "top-level `tabs`, `tabOrder`,
`sheets`" so the document consistently hyphenates "top-level" when used as a
compound adjective (the worksheet-local items `cells`, `rowOrder`, `colOrder`,
`nextRowId`, `nextColId` remain unchanged).

In `@packages/sheet/src/index.ts`:
- Around line 127-141: Export a clearly named factory alias for the
default-document creator: add and export createInitialSpreadsheetDocument as a
callable alias that forwards to the existing initialSpreadsheetDocument factory
so call sites use a verb-like name instead of a value-like name; update any
other "initial..." exports in this module (the ones alongside createWorksheet
and createSpreadsheetDocument) to provide matching createX aliases as well so
factories are consistently named.

In `@packages/sheet/test/helpers/concurrency-driver.ts`:
- Around line 93-98: The JSON.stringify comparison in snapshotMatchesOneOf can
fail if Record keys are in different insertion orders; update
snapshotMatchesOneOf (and/or add a small helper like normalizeSnapshot) to
produce a deterministic representation by sorting the keys of
ConcurrencySnapshot.cells (e.g., iterate Object.keys(snapshot.cells).sort() and
build a new object with those ordered keys, and likewise normalize any nested
objects/arrays if needed) and then compare
JSON.stringify(normalizeSnapshot(candidate)) ===
JSON.stringify(normalizeSnapshot(actual)); this preserves existing behavior but
removes the ordering sensitivity when comparing ConcurrencySnapshot instances.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be16633e-5895-4851-8254-1c108145a256

📥 Commits

Reviewing files that changed from the base of the PR and between 4725b5d and 7336bab.

📒 Files selected for processing (14)
  • docs/design/formula-and-calculator.md
  • docs/design/frontend.md
  • docs/design/sheet.md
  • docs/design/spreadsheet-collaboration.md
  • packages/backend/package.json
  • packages/backend/src/yorkie/worksheet-shape-migration.ts
  • packages/backend/test/jest-e2e.json
  • packages/frontend/src/app/documents/document-detail.tsx
  • packages/frontend/src/app/shared/shared-document.tsx
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts
  • packages/frontend/tests/helpers/two-user-yorkie.ts
  • packages/sheet/src/index.ts
  • packages/sheet/src/model/workbook/worksheet-document.ts
  • packages/sheet/test/helpers/concurrency-driver.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/backend/package.json
  • packages/frontend/tests/helpers/two-user-yorkie.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts

hackerwins and others added 3 commits March 14, 2026 16:42
Define a table-driven concurrency case set for spreadsheet structural
edits and add a fast sheet-level serial-intent matrix.

Add a real two-user Yorkie helper, a passing characterization slice,
and an opt-in failing repro slice so known structure-vs-structure
merge issues are executable without turning the default test lane red.
Move the worksheet/document schema behind a single shared boundary and
reorganize the sheet package model into core, worksheet, workbook,
and pivot layers.

Replace the old coordinate-key and legacy worksheet paths with the
canonical cells/rowOrder/colOrder shape, push Yorkie-specific axis and
structure orchestration into frontend-local helpers, and share the
worksheet types across frontend and backend.

Add Yorkie worksheet-shape migration tooling and CLI, fix sheet barrel
type exports, alias @wafflebase/sheet to source in Vite/Jest configs,
and update doc references for moved model files.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Convert initialSpreadsheetDocument from shared const to factory
  function to prevent cross-caller mutation
- Add all LegacyWorksheet optional properties to isLegacyWorksheetShape
  so objects with only merges, charts, filter, etc. are not misclassified
- Add exhaustive switch defaults in applyStoreOp and applyConcurrencyOp
- Reuse safeWorksheetRecordKeys, createWorksheetAxisId, and
  parseWorksheetCellKey from @wafflebase/sheet instead of maintaining
  duplicate implementations in the frontend

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@hackerwins
hackerwins force-pushed the codex/worksheet-collaboration-boundary branch from 7336bab to fa574c6 Compare March 14, 2026 07:44
hackerwins and others added 2 commits March 14, 2026 16:45
Add same-cell, same-row/column-band, multi-count structural, and
metadata+structure interaction cases to the concurrency matrix.

Extend ConcurrencyOp with set-row-height and set-column-width
variants, add dimension snapshot capture to both MemStore and
Yorkie drivers, and classify new Yorkie cases into serial-intent
and characterization filter lists.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Rename spreadsheet-collaboration.md to collaboration.md — the project
is a spreadsheet, so the prefix is redundant.

Remove the deployment runbook and rehearsal log (operational, not
design), drop the stale "Current Naming State" section, and update
the case coverage list to reflect the 23-case matrix.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@hackerwins
hackerwins force-pushed the codex/worksheet-collaboration-boundary branch from 1fb8d33 to 6779a71 Compare March 14, 2026 07:56
@hackerwins hackerwins changed the title Refactor worksheet collaboration boundary and migration Refactor worksheet collaboration model boundary Mar 14, 2026
@hackerwins
hackerwins merged commit a62a396 into main Mar 14, 2026
3 checks passed
@hackerwins
hackerwins deleted the codex/worksheet-collaboration-boundary branch March 14, 2026 08:38
hackerwins added a commit that referenced this pull request Mar 14, 2026
The antlr4ts BitSet class uses util.inspect.custom as a computed
property. Vite's browser polyfill for util doesn't include the
custom symbol, causing a TypeError when the bundle loads in Node.

Since the backend now imports @wafflebase/sheet at runtime (added
in PR #29), this crash blocks the server from starting. Marking
util as external lets Node.js provide its native implementation.
The frontend imports sheet from source, not this bundle.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
hackerwins added a commit that referenced this pull request Mar 14, 2026
PR #29 added runtime imports from @wafflebase/sheet in the backend,
which surfaced two issues:

1. Docker build: sheet dist bundle must be built before the backend.
   Split into multi-stage build — compilation runs natively on the
   CI host while only prod dependency install runs under QEMU for
   the arm64 target. Build time dropped from 10+ min to ~1.5 min.

2. Sheet bundle: antlr4ts uses util.inspect.custom, which breaks
   when Vite polyfills Node's util for the browser. Externalize
   util in rollup and output .cjs for the CJS entry so Node always
   treats it as CommonJS regardless of the package "type": "module".

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@coderabbitai coderabbitai Bot mentioned this pull request May 8, 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