Skip to content

fix(tables): write single-cell edits as an atomic JSONB patch (fix row-level LWW)#5970

Open
waleedlatif1 wants to merge 2 commits into
stagingfrom
table-cell-atomic-patch
Open

fix(tables): write single-cell edits as an atomic JSONB patch (fix row-level LWW)#5970
waleedlatif1 wants to merge 2 commits into
stagingfrom
table-cell-atomic-patch

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • A table row stores every cell in one jsonb data column. The row-edit paths called updateRow in the default replace mode (a full-object read-modify-write), so two users — or a user and an agent/copilot — editing different cells of the same row concurrently would clobber each other: the last write overwrote the whole row from its stale snapshot (row-level last-write-wins).
  • Pass dataWriteMode: 'patch' (Postgres data = data || patch::jsonb) from the four partial-update callers so each cell edit merges atomically under the row lock:
    • the UI row-edit route (PATCH /api/table/[tableId]/rows/[rowId])
    • the v1 API row-edit route (PATCH /api/v1/tables/[tableId]/rows/[rowId])
    • the copilot table update_row tool
    • the workflow-group cancel-write (writes data: {}; patch makes it a true no-op instead of rewriting — and reverting — the whole column)
  • upsertRow (whole-row by design) and updateRowsByFilter (already uses ||) are unchanged. computedWrite stays unset on the user paths, preserving the normal update lock.

Type of Change

  • Bug fix

Testing

  • Added a route test asserting the UI route opts into patch mode; the updateRow JSONB-patch mechanism is already covered in update-row.test.ts.
  • bun run check:api-validation:strict, tsc --noEmit, and the table + copilot suites (481 + 46 tests) pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…w-level LWW)

A table row stores every cell in one jsonb `data` column, and the row-edit paths
called updateRow in the default replace mode — a full-object read-modify-write. Two
users (or a user and an agent/copilot) editing DIFFERENT cells of the same row
concurrently would clobber each other: whichever write committed last overwrote the
whole row from its stale snapshot.

Pass dataWriteMode: 'patch' (Postgres `data = data || patch::jsonb`) from the four
partial-update callers — the UI row route, the v1 API row route, the copilot table
tool, and the workflow-group cancel-write — so each cell edit merges atomically under
the row lock. upsertRow (whole-row by design) and updateRowsByFilter (already uses
||) are unchanged. computedWrite stays unset on the user paths, preserving the update
lock. Adds a route test asserting the UI route opts into patch mode; the updateRow
patch mechanism itself is already covered.
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 26, 2026 6:52pm

Request Review

@cursor

cursor Bot commented Jul 26, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes how row cell data is persisted on common edit paths; behavior is intentional but touches core table write semantics and concurrent-update correctness.

Overview
Fixes row-level last-write-wins when two actors edit different cells on the same row at once. Row data is one jsonb blob; default updateRow replace mode overwrote the whole object from a stale read.

Partial-update callers now pass dataWriteMode: 'patch' so Postgres merges with data = data || patch under the row lock: app PATCH row route, v1 PATCH row route, copilot update_row, and workflow-group cancel writes (data: {} stays a no-op instead of rewriting the column). User paths still omit computedWrite, so update-lock behavior is unchanged.

Adds a route test that the UI PATCH handler passes { dataWriteMode: 'patch' } into updateRow.

Reviewed by Cursor Bugbot for commit 7aff1ee. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR changes partial table-row writes from full JSONB replacement to atomic JSONB patches.

  • Applies patch mode to the UI and v1 row-edit routes.
  • Applies the same behavior to Copilot update_row.
  • Makes workflow-group cancellation preserve concurrent cell edits while updating execution state.
  • Adds a route test verifying that UI edits select patch mode.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/api/table/[tableId]/rows/[rowId]/route.test.ts Adds focused coverage confirming that the UI row-edit route opts into atomic patch mode.
apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts Changes UI partial row edits to merge only supplied cells through the existing JSONB patch mechanism.
apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts Changes v1 partial row edits to use the same cell-level atomic patch behavior.
apps/sim/lib/copilot/tools/server/table/user-table.ts Prevents Copilot row edits from replacing unrelated cells using a stale row snapshot.
apps/sim/lib/table/workflow-columns.ts Makes execution-only cancellation writes leave the row data JSONB value unchanged.

Sequence Diagram

sequenceDiagram
  participant A as Editor A
  participant B as Editor B
  participant API as Row update caller
  participant DB as PostgreSQL
  A->>API: PATCH cell A
  B->>API: PATCH cell B
  API->>DB: "data = data || patchA::jsonb"
  DB-->>API: Row with cell A updated
  API->>DB: "data = data || patchB::jsonb"
  DB-->>API: Row preserving both edits
Loading

Reviews (2): Last reviewed commit: "test(tables): drop banned inline error-m..." | Re-trigger Greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7aff1ee. Configure here.

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