Skip to content

fix(undo): operation-aware json_patch cell undo via read-modify-write (#425 part 1 of 2)#427

Merged
zknpr merged 12 commits into
mainfrom
fix/json-patch-undo-rmw
Jun 3, 2026
Merged

fix(undo): operation-aware json_patch cell undo via read-modify-write (#425 part 1 of 2)#427
zknpr merged 12 commits into
mainfrom
fix/json-patch-undo-rmw

Conversation

@zknpr
Copy link
Copy Markdown
Owner

@zknpr zknpr commented Jun 3, 2026

Summary

Part 1 of 2 for #425. Fixes the undo-fidelity sub-problem: undo of a JSON cell edit no longer clobbers a concurrent change to a different key of the same cell.

Undo previously replaced the whole cell with the recorded prior document (SET col = priorValue). If a different key changed between the tracked edit and the undo — e.g. the cell is open in a VFS editor tab while it's also edited inline in the grid — that concurrent change was lost. P2 (data fidelity; single-pane/sequential undo was already correct).

Approach — read-modify-write (RMW)

A pure helper computeJsonPatchUndo(current, forwardPatch, prior) reads the cell's current value, restores only the keys the forward merge-patch touched (drawing values from the recorded prior), and writes the full object back; it value-replaces when current/prior/forwardPatch is not a JSON object. All JSON reasoning happens in JS — no json_patch() SQL on the undo path — which eliminates RFC 7396's null=delete ambiguity and json_patch()'s silent-wrong behavior on non-object inputs.

This supersedes the closed PR #426, whose inverse-merge-patch approach was architecturally lossy and was closed with two unresolved P2 edges (nested explicit nulls; non-object current cell). RMW closes that entire class. It reuses #426's test scenarios, the isObject arrays-are-atomic fix (RFC 7396), and the operation/newValue recording plumbing already on main.

Behavior (acceptance scenarios)

  • Edited key restored; concurrent sibling key preserved.
  • A wholly-added object key is removed entirely (no leftover {}).
  • Explicit null restored exactly (including nested) while preserving concurrent siblings.
  • Current cell became non-object (SQL NULL / scalar / array / non-JSON text) → value-replace to prior.
  • Forward patch was a scalar/array whole-doc replace, or prior was SQL NULL → value-replace.
  • Batch undo: per-cell RMW, atomic (WASM SAVEPOINT / native single execBatch).

Verification

  • npx tsc --noEmit clean · node scripts/build.mjs OK (all four bundles + sqlite3.wasm) · full unit suite 375 / 375.
  • Revert-proof: reverting the engine code fails exactly the 6 RMW tests (native ×3, WASM s1/s4/s9); restoring → green.

Scope

Design + plan: docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md, docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved JSON cell-edit undo: undos now restore only the keys touched by the edit, preserving concurrent sibling key changes and handling edge cases to prevent data loss.
  • Tests

    • Added extensive tests validating undo behavior across concurrent sibling updates, nested objects, explicit nulls, precision edge cases, and atomic multi-cell batch undos.

Review Change Stack

zknpr and others added 5 commits June 3, 2026 11:02
…write (#425 part 1)

Design doc for the undo-fidelity half of #425, superseding the closed PR #426
inverse-merge-patch attempt. Read-modify-write (read current cell, restore only
the forward patch's touched keys, write the full object back; else value-replace)
is non-lossy across the edge cases #426 closed unresolved (nested explicit nulls;
non-object current cell) and collapses the edge-case treadmill that caused #426's
review churn. Part 2 (futureStack hot-exit persistence) is a separate spec/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task-by-task TDD plan: pure computeJsonPatchUndo helper, WASM + native engine
read-modify-write wiring, full verification. No version/changelog (1.5.1 ships
with part 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
computeJsonPatchUndo restores only the forward patch's touched keys from the
recorded prior, preserving concurrent sibling edits; value-replaces when current,
prior, or the forward patch is not a JSON object. isObject now excludes arrays
(RFC 7396 atomic). Pure + exhaustively unit-tested; engine wiring follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
undoCellUpdate now reads the current cell and routes json_patch undos through
computeJsonPatchUndo (full-object SET), preserving concurrent sibling edits;
non-json_patch and non-object-current cells value-replace. Batch undos that touch
any json_patch cell are wrapped in a SAVEPOINT for atomicity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
undoModification cell_update now reads current then writes the restored object
(or value-replaces) via computeJsonPatchUndo — matching the WASM engine. Batch
json_patch undos write through one execBatch for atomicity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jun 3, 2026

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

Project Deployment Actions Updated (UTC)
sq-lite-explorer Ready Ready Preview, Comment Jun 3, 2026 11:25am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Warning

Rate limit exceeded

@zknpr has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b2f25ff8-218d-4d36-bbb5-a231e03c163f

📥 Commits

Reviewing files that changed from the base of the PR and between 96d0f89 and e0e6b3b.

📒 Files selected for processing (3)
  • src/core/json-utils.ts
  • tests/unit/json_utils.test.ts
  • tests/unit/sqlite_db.test.ts
📝 Walkthrough

Walkthrough

Adds a read-modify-write undo strategy for JSON cell edits. A pure computeJsonPatchUndo helper detects whether undo can surgically restore only touched keys (preserving concurrent sibling updates) or must fall back to full replacement. Both WASM and native engines integrate the helper with per-cell reads and atomic batching.

Changes

JSON Patch Undo via Read-Modify-Write

Layer / File(s) Summary
Design specification and implementation plan
docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md, docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
Spec documents why the previous inverse-merge-patch approach from PR #426 fails (clobbering concurrent sibling keys); introduces the RMW strategy with computeJsonPatchUndo eligibility rules, restoreInto key-structure restoration, and engine wiring requirements. Plan breaks implementation into four sequential tasks: pure helper + tests, WASM integration + tests, native integration + tests, and verification. Both docs establish binding acceptance criteria and constraints.
Core undo algorithm: computeJsonPatchUndo
src/core/json-utils.ts, tests/unit/json_utils.test.ts
Exports JsonUndoPlan type and computeJsonPatchUndo(currentRaw, forwardPatchRaw, priorRaw) function that parses three JSON values and decides between restore (reconstructing only forward-patch keys while preserving concurrent siblings and explicit nulls) or replace (fallback). Includes parseJsonObject JSON parser and restoreInto recursive walker with depth limiting and empty-object deletion. Updates internal isObject to exclude arrays. Unit tests cover concurrent siblings, empty-object collapse, nested edits, explicit null restoration, and fallback scenarios.
Operation serialization and worker wiring
src/core/operation-serializer.ts, src/workerFactory.ts, tests/unit/operation_serializer.test.ts
Adds serializeOperations(ops) proxy to serialize public DatabaseOperations calls via a promise-chain queue; unit tests validate FIFO serialization and lock-release-on-error. createInProcessWasmDatabaseConnection now wraps operationsFacade with serializeOperations for the in-process path.
WASM engine integration
src/core/engine/wasm/WasmDatabaseEngine.ts, tests/unit/sqlite_db.test.ts
Adds computeJsonPatchUndo import and reworks undoCellUpdate to detect json_patch operations and route them through a dedicated per-cell RMW path wrapped in a SAVEPOINT. New undoOneCell reads current cell, computes undo plan, and applies restored value or priorValue fallback. New readCellValue helper fetches current values with escaped identifiers. Non-json_patch undos continue via bulk batch update. SQLite tests validate sibling-key preservation, object-key removal when no concurrent descendants survive, explicit null restoration, non-JSON fallback, precision edge fallbacks, and batch atomicity across multiple cells.
Native worker integration
src/nativeWorker.ts, tests/unit/nativeWorker.test.ts
Imports computeJsonPatchUndo and rewrites undoModification cell_update branch to compute undo values by reading current DB JSON. Batch undo performs per-cell reads followed by atomic execBatch write; single-cell undo computes and writes via single run. Refactors internal impl to rawOperations and wraps public facade with serializeOperations; test infra adds RecordedNativeResponder callbacks to verify SELECT-then-UPDATE sequences, restored-object params, fallback to priorValue for non-JSON cells, batch atomicity, and serialization ordering.

Sequence Diagram

sequenceDiagram
  participant Editor as Client Edit
  participant WASM as WASM Engine
  participant Native as Native Worker
  participant JSON as computeJsonPatchUndo
  participant SQLite
  
  Editor->>WASM: undoModification(json_patch cell)
  WASM->>SQLite: readCellValue (current JSON)
  SQLite-->>WASM: currentValue
  WASM->>JSON: compute(current, forward, prior)
  JSON-->>WASM: JsonUndoPlan (restore|replace)
  WASM->>SQLite: updateCellBatch (restored value)
  
  Editor->>Native: undoModification(json_patch cell batch)
  Native->>SQLite: query (current JSON per cell)
  SQLite-->>Native: [current1, current2, ...]
  Native->>JSON: compute per cell
  JSON-->>Native: [plan1, plan2, ...]
  Native->>SQLite: execBatch (atomic UPDATEs)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A patch undid, but siblings stayed,
Read-modify-write, the perfect trade!
No clobber now when edits swirl—
Each key restored in SQL's pure world.
—The Fix Rabbit 🔧✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/json-patch-undo-rmw

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements an operation-aware json_patch cell undo mechanism using a read-modify-write (RMW) approach in JavaScript/TypeScript. It introduces a pure decision helper computeJsonPatchUndo in json-utils.ts to surgically restore only the keys touched by a forward patch while preserving concurrent sibling edits, and integrates this logic into both the WASM and native database engines. Reviewers suggested simplifying and optimizing the batch undo logic in both engines: first, by leveraging the existing updateCellBatch method in the WASM engine to avoid sequential query overhead and manual savepoint management; and second, by unifying the batch update path in the native worker to eliminate redundant conditional branching.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 286 to +317
if (affectedCells) {
// Batch undo
const updates = affectedCells.map(cell => ({
rowId: cell.rowId,
column: cell.columnName,
value: cell.priorValue ?? null
}));
await this.updateCellBatch(targetTable, updates);
const hasJsonPatchUndo = affectedCells.some(cell => cell.operation === 'json_patch');
if (hasJsonPatchUndo) {
// JSON-patch undo reads current cells before writing restored values, so
// the whole history entry is protected by one savepoint for atomicity.
const savepointName = this.createSavepointName('sp_undo_cell_update');
await this.executeQuery(`SAVEPOINT ${savepointName}`);
try {
for (const cell of affectedCells) {
await this.undoOneCell(
targetTable,
cell.rowId,
cell.columnName,
cell.priorValue,
cell.newValue,
cell.operation
);
}
await this.executeQuery(`RELEASE ${savepointName}`);
} catch (err) {
await this.safeRollbackSavepoint(savepointName, 'undoCellUpdate');
throw err;
}
} else {
// Pure value-replacement batches keep the existing batch update path.
const updates = affectedCells.map(cell => ({
rowId: cell.rowId,
column: cell.columnName,
value: cell.priorValue ?? null
}));
await this.updateCellBatch(targetTable, updates);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

We can simplify the batch undo logic and significantly improve performance by reusing the existing updateCellBatch method. Instead of executing individual updateCell queries sequentially in a loop (which incurs high query overhead), we can collect all the computed restored values first and then perform a single batched update. This also eliminates the need for manual savepoint/transaction management here, as updateCellBatch already handles it atomically.

    if (affectedCells) {
      const updates: CellUpdate[] = [];
      for (const cell of affectedCells) {
        let value = cell.priorValue ?? null;
        if (cell.operation === 'json_patch') {
          const currentValue = await this.readCellValue(targetTable, cell.rowId, cell.columnName);
          const plan = computeJsonPatchUndo(currentValue, cell.newValue, cell.priorValue);
          if (plan.kind === 'restore') {
            value = plan.value;
          }
        }
        updates.push({
          rowId: cell.rowId,
          column: cell.columnName,
          value
        });
      }
      await this.updateCellBatch(targetTable, updates);
    }

Comment thread src/nativeWorker.ts
Comment on lines 569 to +605
if (affectedCells) {
// Batch undo
const updates = affectedCells.map(c => ({
rowId: c.rowId,
column: c.columnName,
value: c.priorValue
} as CellUpdate));
await worker.call('execBatch', [
updates.map(u => ({
sql: `UPDATE ${escapeIdentifier(targetTable)} SET ${escapeIdentifier(u.column)} = ? WHERE rowid = ?`,
params: [u.value, Number(u.rowId)]
}))
]);
const hasJsonPatchUndo = affectedCells.some(cell => cell.operation === 'json_patch');
if (hasJsonPatchUndo) {
// Read and compute all values first, then send one execBatch
// so native SQLite applies the write set atomically.
const batchItems: { sql: string; params: CellValue[] }[] = [];
for (const cell of affectedCells) {
const rowIdNum = validateRowId(cell.rowId);
const value = await resolveUndoValue(
cell.rowId,
cell.columnName,
cell.priorValue,
cell.newValue,
cell.operation
);
batchItems.push({
sql: `UPDATE ${escapeIdentifier(targetTable)} SET ${escapeIdentifier(cell.columnName)} = ? WHERE rowid = ?`,
params: [value, rowIdNum]
});
}
if (batchItems.length > 0) {
await worker.call('execBatch', [batchItems]);
}
} else {
// Pure value-replacement batches keep the existing SQL shape.
const updates = affectedCells.map(c => ({
rowId: c.rowId,
column: c.columnName,
value: c.priorValue
} as CellUpdate));
await worker.call('execBatch', [
updates.map(u => ({
sql: `UPDATE ${escapeIdentifier(targetTable)} SET ${escapeIdentifier(u.column)} = ? WHERE rowid = ?`,
params: [u.value, Number(u.rowId)]
}))
]);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

We can simplify the batch undo logic by removing the conditional branching on hasJsonPatchUndo. Since resolveUndoValue immediately returns cellPrior ?? null without any database query when the operation is not json_patch, we can run the same unified loop for all cells. This removes code duplication and keeps the batch update logic clean and consistent.

              if (affectedCells) {
                const batchItems: { sql: string; params: CellValue[] }[] = [];
                for (const cell of affectedCells) {
                  const rowIdNum = validateRowId(cell.rowId);
                  const value = await resolveUndoValue(
                    cell.rowId,
                    cell.columnName,
                    cell.priorValue,
                    cell.newValue,
                    cell.operation
                  );
                  batchItems.push({
                    sql: `UPDATE ${escapeIdentifier(targetTable)} SET ${escapeIdentifier(cell.columnName)} = ? WHERE rowid = ?`,
                    params: [value, rowIdNum]
                  });
                }
                if (batchItems.length > 0) {
                  await worker.call('execBatch', [batchItems]);
                }
              }

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md`:
- Line 97: The fenced code block that begins with the triple backticks at Line
97 is missing a language tag and triggers markdownlint MD040; update that
opening fence (the ``` line) to include an appropriate language identifier
(e.g., ```json or ```diff) so the block is language-highlighted and the lint
rule is satisfied.
- Line 43: The ATX heading on the line that begins "`#426` built an **inverse
merge patch** (`invertMergePatch` / `tryCreateInverseMergePatch`) and applied"
is missing the required space after the "##"; update the heading to include a
space after the hashes (e.g., "## 426 built ...") so it conforms to MD018 and
renders correctly; the change touches the heading text referencing
invertMergePatch and tryCreateInverseMergePatch.

In `@src/core/json-utils.ts`:
- Around line 165-190: The code currently uses plain object literals and spread
({ ...currentObj }) which allow special keys like "__proto__" to mutate
prototypes; update restoreInto so temporary containers are created with no
prototype and only own properties are copied: replace "const result:
Record<string, unknown> = { ...currentObj }" with creating result via
Object.create(null) and copy currentObj's own keys into it (e.g., for (const k
of Object.keys(currentObj)) result[k] = currentObj[k]); similarly ensure "base"
and any other temporary nested containers use Object.create(null) and
copy-only-own-properties before calling restoreInto, and continue to use
Object.prototype.hasOwnProperty.call(priorObj, key) for existence checks to
avoid prototype side-effects.

In `@src/nativeWorker.ts`:
- Around line 543-617: The current json_patch undo path calls
operationsFacade.executeQuery per cell inside resolveUndoValue, causing many IPC
round-trips; instead, collect all SELECT statements for affectedCells (using
validateRowId and escapeIdentifier on column/targetTable), call
worker.call('queryBatch') once to fetch current values in the same order, then
for each cell run computeJsonPatchUndo(currentValue, cell.newValue,
cell.priorValue) and build batchItems for worker.call('execBatch') as before;
ensure you preserve the mapping order between affectedCells and queryBatch
results and keep resolveUndoValue logic but refactor it to consume pre-fetched
current values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e34fea64-b459-49e4-8304-5f9de88a71f2

📥 Commits

Reviewing files that changed from the base of the PR and between e726b01 and 80d821f.

📒 Files selected for processing (8)
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/core/json-utils.ts
  • src/nativeWorker.ts
  • tests/unit/json_utils.test.ts
  • tests/unit/nativeWorker.test.ts
  • tests/unit/sqlite_db.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
tests/unit/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

tests/unit/*.test.ts: Use Object.defineProperty(obj, prop, { value, writable: true, configurable: true }) for setting readonly VS Code API fields like vscode.env.uiKind in unit tests
Import tests/unit/vscode_mock_setup.ts at the beginning of unit test files to ensure VS Code mocks are initialized before running tests

Files:

  • tests/unit/sqlite_db.test.ts
  • tests/unit/nativeWorker.test.ts
  • tests/unit/json_utils.test.ts
src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.ts: Use prepared statements with ? placeholders for all query parameter values to prevent SQL injection
Always use escapeIdentifier() function for table and column names in SQL queries to prevent identifier-based SQL injection
Use validateSqlType() for all user-provided SQL types in DDL statements to prevent type-based SQL injection
Validate PRAGMA string values with the regex /^[a-zA-Z0-9_-]+$/ whitelist and check numeric PRAGMA values with Number.isFinite()
Use escapeLikePattern() for user input in LIKE queries with the ESCAPE '\\' clause to prevent LIKE wildcard injection
Use zero-copy transfer for large binary data (ArrayBuffers) in RPC communication by wrapping with the Transfer wrapper
Use SAVEPOINT/RELEASE/ROLLBACK TO instead of BEGIN TRANSACTION in updateCellBatch to safely handle nested transactions
Use the safeRollback(context) helper when handling transaction errors to log failures instead of throwing, preventing secondary rollback errors
Check for SQLite json_patch() availability at engine construction time and use it in UPDATE statements when available, falling back to JS-side applyMergePatch() when unavailable
Use getNodeFs() from sqlite-db.ts to safely require the Node.js fs module, which returns undefined in browser environments
Check import.meta.env.VSCODE_BROWSER_EXT to conditionally handle environment-specific code paths for browser vs Node.js platforms
Use the Core RPC protocol defined in src/core/rpc.ts for all Worker communication and when the Extension invokes Webview methods
Use buildMethodProxy() from src/core/rpc.ts to create proxy objects that automatically serialize RPC calls to workers or the webview
Record database modifications in ModificationTracker via recordModification() before committing changes to track undo/redo history
Write all executed SQL (both read and write operations) to the 'SQLite Explorer' output channel via GlobalOutputChannel?.appendLine() for debugging...

Files:

  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
{src/**/*.ts,core/ui/modules/*.js}

📄 CodeRabbit inference engine (CLAUDE.md)

Serialize Uint8Array using the marker format { __type: 'Uint8Array', base64: '...' } with exactly 2 keys to prevent collisions with user data

Files:

  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
src/nativeWorker.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use queryBatch() in the native worker to send multiple SQL queries in a single IPC round-trip for improved performance

Files:

  • src/nativeWorker.ts
src/{databaseWorker,nativeWorker}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Route worker logs through RPC using LogEnvelope format { kind: 'log', level, args } instead of console.* to ensure proper log handling

Files:

  • src/nativeWorker.ts
🧠 Learnings (33)
📓 Common learnings
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:38.498Z
Learning: Use JSON Merge Patch (RFC 7396) for cell updates with `json_patch()` when available, falling back to JS-side `applyMergePatch()` from `json-utils.ts`.
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/sqlite-db.ts : Use `json_patch()` SQLite function when available for cell updates (JSON1 extension); fall back to JS-side `applyMergePatch()` when unavailable
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:49.264Z
Learning: Applies to src/**/*.{ts,tsx} : Use JSON Patch (RFC 7396) via `applyMergePatch()` from `json-utils.ts` for cell updates when SQLite's `json_patch()` is unavailable
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:03.123Z
Learning: Applies to src/core/json-utils.ts : Apply JSON Merge Patch (RFC 7396) using generate() and apply() from json-utils.ts for cell patch operations
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:22.670Z
Learning: Applies to src/**/*.ts : Use `SAVEPOINT`/`RELEASE`/`ROLLBACK TO` instead of `BEGIN TRANSACTION` in `updateCellBatch` to safely handle nested transactions
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:42.846Z
Learning: Applies to tests/unit/**/*.test.ts : Test JSON patch behavior (merged result), not implementation (SQL function)
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:32.320Z
Learning: Applies to tests/unit/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (specific SQL functions).
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/core/sqlite-db.ts : Probe for SQLite `json_patch()` at engine construction time to optimize `updateCell` and `updateCellBatch` operations
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:28.282Z
Learning: Applies to tests/**/*.test.ts : Test JSON patch behavior (merged result) not implementation (SQL function) to ensure correctness
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:51.645Z
Learning: Applies to tests/unit/*.test.ts : Test JSON patch behavior (merged result) not implementation (SQL function) in unit tests
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/sqlite-db.ts : Probe for SQLite's `json_patch()` at engine construction time and use `json_patch(COALESCE(col, '{}'), ?)` in single UPDATE when available instead of SELECT round-trip
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/core/sqlite-db.ts : Use `json_patch(COALESCE(col, '{}'), ?)` in a single UPDATE when JSON1 extension is available; fall back to JS-side `applyMergePatch()` otherwise
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:00.429Z
Learning: Applies to src/core/sqlite-db.ts : Probe for SQLite's `json_patch()` at engine construction time; use `json_patch(COALESCE(col, '{}'), ?)` when available, otherwise fall back to JS-side `applyMergePatch()`
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:32.320Z
Learning: Applies to src/sqlite-db.ts : Use JSON1 extension's `json_patch()` function when available; fall back to JS-side `applyMergePatch()` when unavailable.
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/sqlite-db.ts : Probe for SQLite's `json_patch()` at engine construction time and use the `hasJsonPatch` flag to conditionally optimize JSON updates.
📚 Learning: 2026-03-15T21:35:32.320Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:32.320Z
Learning: Applies to tests/unit/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (specific SQL functions).

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-04T19:34:42.846Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:42.846Z
Learning: Applies to tests/unit/**/*.test.ts : Test JSON patch behavior (merged result), not implementation (SQL function)

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-03-15T21:35:51.645Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:51.645Z
Learning: Applies to tests/unit/*.test.ts : Test JSON patch behavior (merged result) not implementation (SQL function) in unit tests

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-03-15T21:35:42.332Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:42.332Z
Learning: Applies to tests/unit/**/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (SQL function usage)

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-04-04T19:34:49.264Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:49.264Z
Learning: Applies to tests/unit/**/*.test.ts : Test behavior (merged result) not implementation (SQL function) for JSON patch functionality

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
  • src/nativeWorker.ts
📚 Learning: 2026-03-15T21:35:28.282Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:28.282Z
Learning: Applies to tests/**/*.test.ts : Test JSON patch behavior (merged result) not implementation (SQL function) to ensure correctness

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-04-04T19:34:38.498Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:38.498Z
Learning: Applies to src/**/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (SQL function usage).

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • tests/unit/nativeWorker.test.ts
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-04-23T12:32:55.133Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/sqlite-db.ts : Use `json_patch()` SQLite function when available for cell updates (JSON1 extension); fall back to JS-side `applyMergePatch()` when unavailable

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T12:32:55.133Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/core/sqlite-db.ts : Probe for SQLite `json_patch()` at engine construction time to optimize `updateCell` and `updateCellBatch` operations

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T12:32:55.133Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/sqlite-db.ts : Probe for SQLite's `json_patch()` at engine construction time and use `json_patch(COALESCE(col, '{}'), ?)` in single UPDATE when available instead of SELECT round-trip

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • src/nativeWorker.ts
📚 Learning: 2026-04-04T19:35:02.965Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:35:02.965Z
Learning: Applies to tests/unit/*.test.ts : Test behavior (merged result) not implementation when testing JSON patch operations

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • tests/unit/nativeWorker.test.ts
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-04-23T15:21:00.429Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:00.429Z
Learning: Applies to tests/unit/**/*.test.ts : Test behavior (merged result) not implementation when testing JSON patch operations

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • tests/unit/nativeWorker.test.ts
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-04-04T19:34:38.498Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:38.498Z
Learning: Use JSON Merge Patch (RFC 7396) for cell updates with `json_patch()` when available, falling back to JS-side `applyMergePatch()` from `json-utils.ts`.

Applied to files:

  • tests/unit/sqlite_db.test.ts
  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:03.123Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:03.123Z
Learning: Applies to src/core/json-utils.ts : Apply JSON Merge Patch (RFC 7396) using generate() and apply() from json-utils.ts for cell patch operations

Applied to files:

  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • tests/unit/json_utils.test.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-04T19:34:49.264Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:49.264Z
Learning: Applies to src/**/*.{ts,tsx} : Use JSON Patch (RFC 7396) via `applyMergePatch()` from `json-utils.ts` for cell updates when SQLite's `json_patch()` is unavailable

Applied to files:

  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md
  • src/nativeWorker.ts
📚 Learning: 2026-03-15T21:35:30.312Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:30.312Z
Learning: Applies to **/*.{ts,tsx,js} : Use JSON Merge Patch (RFC 7396) via `json-utils.ts` for applying incremental updates to JSON data

Applied to files:

  • src/core/json-utils.ts
  • tests/unit/json_utils.test.ts
📚 Learning: 2026-03-15T21:35:32.320Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:32.320Z
Learning: Applies to src/sqlite-db.ts : Use JSON1 extension's `json_patch()` function when available; fall back to JS-side `applyMergePatch()` when unavailable.

Applied to files:

  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T12:32:55.133Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/core/sqlite-db.ts : Use `json_patch(COALESCE(col, '{}'), ?)` in a single UPDATE when JSON1 extension is available; fall back to JS-side `applyMergePatch()` otherwise

Applied to files:

  • src/core/json-utils.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:00.429Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:00.429Z
Learning: Applies to src/core/sqlite-db.ts : Probe for SQLite's `json_patch()` at engine construction time; use `json_patch(COALESCE(col, '{}'), ?)` when available, otherwise fall back to JS-side `applyMergePatch()`

Applied to files:

  • src/core/json-utils.ts
  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-04T18:52:38.095Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T18:52:38.095Z
Learning: Applies to src/sqlite-db.ts : Fall back to JS-side `applyMergePatch()` from `json-utils.ts` when the JSON1 extension is unavailable

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:03.123Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:03.123Z
Learning: Applies to src/core/sqlite-db.ts : Fall back to JS-side applyMergePatch() from json-utils.ts when SQLite JSON1 extension is unavailable

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:22.670Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:22.670Z
Learning: Applies to src/**/*.ts : Check for SQLite `json_patch()` availability at engine construction time and use it in UPDATE statements when available, falling back to JS-side `applyMergePatch()` when unavailable

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:22.670Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:22.670Z
Learning: Applies to src/**/*.ts : Use `SAVEPOINT`/`RELEASE`/`ROLLBACK TO` instead of `BEGIN TRANSACTION` in `updateCellBatch` to safely handle nested transactions

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • src/nativeWorker.ts
📚 Learning: 2026-03-15T21:35:51.645Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:51.645Z
Learning: Applies to src/core/undo-history.ts : Track database modifications using `ModificationTracker` before saving to support undo/redo operations

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-04T19:35:02.965Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:35:02.965Z
Learning: Applies to src/core/undo-history.ts : Track database modifications in `ModificationTracker` before saving to enable undo/redo functionality

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T16:37:35.582Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T16:37:35.582Z
Learning: Applies to src/core/sqlite-db.ts : Use `SAVEPOINT`/`RELEASE`/`ROLLBACK TO` instead of `BEGIN TRANSACTION` in `updateCellBatch` to allow safe nesting within outer transactions

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • docs/superpowers/plans/2026-06-03-json-patch-undo-rmw.md
  • tests/unit/nativeWorker.test.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T16:37:35.582Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T16:37:35.582Z
Learning: Applies to src/core/sqlite-db.ts : Use SAVEPOINT/RELEASE/ROLLBACK TO instead of BEGIN TRANSACTION in updateCellBatch to ensure transaction safety when called from outer transactions

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:03.123Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:03.123Z
Learning: Applies to src/core/undo-history.ts : Track all database modifications in ModificationTracker from undo-history.ts for undo/redo support

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-04T18:52:25.579Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T18:52:25.579Z
Learning: Applies to src/databaseModel.ts : Implement DatabaseDocument with support for undo/redo via ModificationTracker and save/revert via VS Code API

Applied to files:

  • src/core/engine/wasm/WasmDatabaseEngine.ts
  • src/nativeWorker.ts
📚 Learning: 2026-04-23T12:32:55.133Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T12:32:55.133Z
Learning: Applies to src/nativeWorker.ts : Use `batched` IPC calls when executing multiple related SQL queries to reduce round-trip overhead (e.g., schema fetching via `queryBatch`).

Applied to files:

  • tests/unit/nativeWorker.test.ts
📚 Learning: 2026-03-15T21:35:42.332Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:42.332Z
Learning: Applies to src/**/*.ts : Probe for SQLite's `json_patch()` function at engine construction time and use `json_patch(COALESCE(col, '{}'), ?)` when available to avoid SELECT round-trips

Applied to files:

  • src/nativeWorker.ts
📚 Learning: 2026-04-23T15:21:22.670Z
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:22.670Z
Learning: Applies to src/**/*.ts : Record database modifications in `ModificationTracker` via `recordModification()` before committing changes to track undo/redo history

Applied to files:

  • src/nativeWorker.ts
🪛 markdownlint-cli2 (0.22.1)
docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md

[warning] 43-43: No space after hash on atx style heading

(MD018, no-missing-space-atx)


[warning] 97-97: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (9)
src/core/engine/wasm/WasmDatabaseEngine.ts (3)

27-27: LGTM!


284-321: LGTM!


323-356: LGTM!

tests/unit/sqlite_db.test.ts (1)

247-398: LGTM!

src/nativeWorker.ts (1)

41-41: LGTM!

tests/unit/nativeWorker.test.ts (3)

18-18: LGTM!

Also applies to: 28-28, 54-57


203-220: LGTM!


290-431: LGTM!

tests/unit/json_utils.test.ts (1)

2-5: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Import the VS Code mock setup first.

This file matches tests/unit/*.test.ts, but the required tests/unit/vscode_mock_setup.ts bootstrap import is still missing from the top of the file. Please add it before the other imports so the unit-test environment stays consistent with the repo harness. As per coding guidelines, "Import tests/unit/vscode_mock_setup.ts at the beginning of unit test files to ensure VS Code mocks are initialized before running tests".

⛔ Skipped due to learnings
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:51.645Z
Learning: Applies to tests/unit/*.test.ts : Test JSON patch behavior (merged result) not implementation (SQL function) in unit tests
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:32.320Z
Learning: Applies to tests/unit/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (specific SQL functions).
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:42.846Z
Learning: Applies to tests/unit/**/*.test.ts : Test JSON patch behavior (merged result), not implementation (SQL function)
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:42.332Z
Learning: Applies to tests/unit/**/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (SQL function usage)
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:35:02.965Z
Learning: Applies to tests/unit/*.test.ts : Test behavior (merged result) not implementation when testing JSON patch operations
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-23T15:21:00.429Z
Learning: Applies to tests/unit/**/*.test.ts : Test behavior (merged result) not implementation when testing JSON patch operations
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:49.264Z
Learning: Applies to tests/unit/**/*.test.ts : Test behavior (merged result) not implementation (SQL function) for JSON patch functionality
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:35:28.282Z
Learning: Applies to tests/**/*.test.ts : Test JSON patch behavior (merged result) not implementation (SQL function) to ensure correctness
Learnt from: CR
Repo: zknpr/SQLite-Explorer PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:34:38.498Z
Learning: Applies to src/**/*.test.ts : Test JSON patch behavior (merged result) rather than implementation details (SQL function usage).
Learnt from: zknpr
Repo: zknpr/SQLite-Explorer PR: 0
File: :0-0
Timestamp: 2026-05-31T20:57:58.186Z
Learning: In the zknpr/SQLite-Explorer repository, unit tests in `tests/unit/` only import `./vscode_mock_setup` when they directly or transitively touch a `vscode`-importing module. Pure-logic tests (e.g., `threadPool.test.ts`, `rpc-protocol.test.ts`, `json-utils.test.ts`) intentionally omit the mock import. As of 2026-05, 13 of 35 unit test files do not import the mock. New test files that do not load any `vscode`-importing modules should NOT import `./vscode_mock_setup`.

Comment thread docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md Outdated
Comment thread docs/superpowers/specs/2026-06-03-json-patch-undo-rmw-design.md Outdated
Comment thread src/core/json-utils.ts Outdated
Comment thread src/nativeWorker.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80d821f712

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/json-utils.ts Outdated

if (isObject(pv)) {
if (priorHas && isObject(priorVal)) {
const base = isObject(result[key]) ? (result[key] as Record<string, unknown>) : {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back when patched nested value is no longer an object

When a nested object that was patched has since been replaced by a scalar or array, this branch uses {} as the restore base and writes back only the keys present in the forward patch. For example, undoing a patch from {meta:{reviewed:false, owner:"ada"}} to {meta:{reviewed:true, owner:"ada"}} after a concurrent edit changed meta to "archived" produces {meta:{reviewed:false}}, dropping the untouched prior owner field instead of doing the same kind of safe replacement used for non-object current cells at the root. Since surgical restore is not possible once the nested current value is non-object, this should fall back to the prior subtree or full prior value rather than constructing a partial prior object.

Useful? React with 👍 / 👎.

Comment thread src/nativeWorker.ts Outdated
Comment on lines +556 to +558
const read = await operationsFacade.executeQuery(
`SELECT ${escapeIdentifier(column)} FROM ${escapeIdentifier(targetTable)} WHERE rowid = ?`,
[rowIdNum]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep native JSON undo read/write atomic

In the native path this SELECT is a separate RPC from the later run/execBatch write, so if another database operation is queued while undoModification is awaiting the read, the worker can process that operation before the undo write is sent. In that case a sibling key added to the same JSON cell after this read is not included in computeJsonPatchUndo and is overwritten by the subsequent full-cell SET, leaving the native engine with the same clobbering race this change is meant to avoid. The read/compute/write needs to be serialized in one worker operation or otherwise guarded by a native-side transaction/mutex.

Useful? React with 👍 / 👎.

Comment thread src/core/json-utils.ts
if (!current || !forwardPatch || !prior) {
return { kind: 'replace' };
}
return { kind: 'restore', value: JSON.stringify(restoreInto(current, forwardPatch, prior, 0)) };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve JSON numbers outside JS precision during undo

For JSON cells that contain large integers or high-precision numbers, this new undo path parses the entire current/prior documents into JavaScript numbers and then writes the whole document back with JSON.stringify. Even if the patch only touched a different key, a value like 9007199254740993 in an untouched sibling is rounded during this parse/stringify cycle, so undoing a JSON patch can corrupt data that the previous raw priorValue replacement preserved exactly. The restore should avoid reparsing/re-emitting untouched numeric tokens or fall back when exact JSON fidelity cannot be maintained.

Useful? React with 👍 / 👎.

zknpr and others added 4 commits June 3, 2026 12:24
… guard (review)

Address PR #427 review of restoreInto/computeJsonPatchUndo:
- CR3 (CodeRabbit, Major): plain object literals + spread let a JSON key like
  __proto__/constructor mutate the temp object's prototype. Build containers with
  Object.create(null) + hasOwnProperty guards so such keys are treated as data.
- CX1 (Codex, P2): when a concurrent edit replaced a patched nested object with a
  scalar, restore built a partial object and dropped untouched prior siblings. Fall
  back to the full prior subtree when the current nested value is not an object.
- CX3 (Codex, P2): RMW reparsed the whole cell, corrupting JSON integers > 2^53 even
  in untouched siblings (regression vs verbatim value-replace). hasPrecisionRiskyInteger
  detects them and value-replaces (byte-exact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #427 review G1 (Gemini): the manual SAVEPOINT + per-cell updateCell loop
duplicated atomicity that updateCellBatch already provides. Compute each cell's undo
value, then write via a single updateCellBatch. sqlite_db tests cover the nested-object
fallback and precision-risky-integer cases through the real engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view)

Address PR #427 review:
- CX2 (Codex, P2): native json_patch undo read+write were separate worker RPCs, so a
  concurrent same-cell write could interleave and re-introduce the clobber. Add a
  per-connection promise-chain operation lock around the public operations; internal
  facade-to-facade calls use the raw (unlocked) implementation to avoid deadlock.
  Worker ops are already FIFO, so the lock only keeps each operation's messages
  contiguous.
- G2 (Gemini): drop the hasJsonPatchUndo branch; one unified compute/write path.
- CR4 (CodeRabbit): batch the per-cell SELECT reads into a single queryBatch round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #427 review CR1/CR2 (CodeRabbit): prefix #426 so the line is not parsed as
an ATX heading (MD018); tag the pseudocode fence as text (MD040).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zknpr
Copy link
Copy Markdown
Owner Author

zknpr commented Jun 3, 2026

Review feedback addressed — all 9 findings

Across 4 commits (a389f84, 2e52b44, 3351523, 44f2b72). Full unit suite 384/384, tsc --noEmit clean, build OK.

Finding Resolution Commit
CR3 (Major, __proto__) restoreInto builds null-prototype containers + hasOwnProperty guards, so __proto__/constructor are treated as data. Test asserts exact serialization and Object.prototype is untouched. a389f84
CX1 (P2, nested non-object) Falls back to the full prior subtree when the current nested value is no longer an object — no longer drops untouched prior siblings (e.g. owner). a389f84
CX3 (P2, number precision) hasPrecisionRiskyInteger → value-replace for cells containing integers > 2⁵³, keeping them byte-exact (RMW reparse was a regression vs the old verbatim restore). Engine test asserts 9007199254740993 survives undo unrounded. a389f84
G1 (Med, WASM) Manual SAVEPOINT + per-cell loop → single updateCellBatch (already atomic). 2e52b44
G2 (Med, native) Dropped the hasJsonPatchUndo branch; one unified compute/write path. 3351523
CR4 (Trivial, native) Batch the per-cell SELECT reads into a single queryBatch round-trip (order-preserving). 3351523
CX2 (P2, native atomicity) Per-connection promise-chain operation lock so a json_patch undo's read/compute/write cannot interleave with a concurrent same-cell write; internal facade→facade calls use the raw (unlocked) impl to avoid deadlock. Regression test asserts a concurrent updateCell cannot write between undo's read and write. 3351523
CR1/CR2 (Minor) MD018/MD040 markdown-lint fixes in the design spec. 44f2b72

Note: automated reviewers run on PR open only, so these fixes won't trigger a re-review pass.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44f2b727c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/json-utils.ts Outdated
Comment on lines +214 to +216
const structural = raw.replace(/"(?:\\.|[^"\\])*"/g, '""');
const digitRuns = structural.match(/\d{16,}/g);
return !!digitRuns && digitRuns.some(run => !Number.isSafeInteger(Number(run)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat overflowing JSON exponents as unsafe before restoring

Although the new guard checks long digit runs, it still lets valid JSON numbers such as 1e999 through because neither digit run has 16+ digits. In a json_patch undo where that number is an untouched sibling (for example current/prior contain {"huge":1e999,"a":2} and only a is being restored), JSON.parse turns it into Infinity and the restore path then writes it back with JSON.stringify as null, corrupting data that the previous full prior-value replacement preserved. The unsafe-number detection needs to reject exponent/overflow tokens before allowing the parse/stringify surgical restore.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 71cb539. hasPrecisionRiskyIntegerhasPrecisionRiskyNumber, which now also rejects any token that parses to a non-finite number (covers 1e999Infinity), so those cells value-replace and stay byte-exact. Engine test asserts a {"huge":1e999,...} cell survives a json_patch undo with huge unchanged (not null).

operation: ModificationEntry['operation']
): Promise<CellValue> {
if (operation === 'json_patch') {
const currentValue = await this.readCellValue(table, rowId, column);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Serialize WASM json_patch undo read/write

In the browser WASM path, src/workerFactory.ts exposes undoModification and updateCell by calling the in-process endpoint directly, so another public edit can run after this awaited read but before the later updateCell/updateCellBatch write. For example, if a json_patch undo reads {a:2,b:1} here and a grid/VFS edit then adds c before the undo continuation resumes, the undo writes the restored object computed from the stale read and drops c; the native path added a facade lock for this same read/compute/write sequence, but the WASM endpoint does not have an equivalent guard.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 96d0f89. Correct — the native CX2 lock didn't cover the WASM in-process path. Extracted the per-connection promise-chain lock into a shared serializeOperations() (Proxy over DatabaseOperations; passes engineKind/dispose through; deadlock-free since internal facade calls bypass it) and applied it to both facades: native (replacing the inline lock) and the WASM in-process facade in workerFactory. The worker-thread WASM path is one-RPC-per-op, so it's already atomic and isn't wrapped. Added a serializeOperations unit test (FIFO + error-release) alongside the existing native serialization test.

zknpr and others added 2 commits June 3, 2026 13:12
…view)

Address PR #427 Codex re-review (json-utils.ts:216): the precision guard only checked
16+ digit integer runs, so an exponent like 1e999 passed; RMW then JSON.parse'd it to
Infinity and wrote it back as null, corrupting an untouched sibling. Rename
hasPrecisionRiskyInteger -> hasPrecisionRiskyNumber and also reject any token that
parses to a non-finite number, so those cells value-replace (byte-exact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ck (review)

Address PR #427 Codex re-review (WasmDatabaseEngine.ts:327): the native CX2 fix did
not cover the WASM in-process path, where workerFactory exposes undoModification and
updateCell as direct endpoint calls, so a concurrent edit could still interleave
between a json_patch undo's read and write.

Extract the per-connection promise-chain lock into a shared serializeOperations()
(Proxy over DatabaseOperations; passes through engineKind/dispose symbols; deadlock-free
since internal facade calls bypass it) and apply it to BOTH facades: native (replacing
the inline lock) and the WASM in-process facade in workerFactory. The worker-thread WASM
path is one-RPC-per-op and needs no lock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96d0f8927f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/json-utils.ts Outdated
return tokens.some(tok => {
const n = Number(tok);
if (!Number.isFinite(n)) return true;
if (/^-?\d+$/.test(tok)) return !Number.isSafeInteger(n);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat unsafe decimal JSON numbers as replace-only

The fresh evidence in this revision is that hasPrecisionRiskyNumber only rejects integer tokens here; fractional tokens always return safe. When a json_patch undo touches a different key in a cell like {"precise":0.1234567890123456789012345,"a":2}, computeJsonPatchUndo takes the surgical path, JSON.parse rounds the untouched decimal, and JSON.stringify writes it back as 0.12345678901234568, corrupting data that the replace fallback would have preserved. The guard should also reject decimal tokens that cannot round-trip exactly before allowing the parse/stringify restore path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in e0e6b3b. Replaced the integer-only check with a uniform round-trip test — a token is risky if non-finite or JSON.stringify(Number(tok)) !== tok — so large integers, exponent overflow (1e999), and high-precision decimals (0.1234567890123456789012345) all fall back to byte-exact value-replace in one rule. Pure + engine tests assert the decimal survives undo unrounded. Full suite 390/390 (no over-flag regression — canonical numbers still take the surgical path).

Address PR #427 Codex round-3 (json-utils.ts:219): the precision guard accepted
high-precision decimals like 0.1234567890123456789012345, which an RMW reparse rounds,
corrupting an untouched sibling. Replace the integer-only check with a uniform
round-trip test — a token is risky if non-finite or JSON.stringify(Number(tok)) !== tok
— covering large integers, overflow, and high-precision decimals in one rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zknpr zknpr merged commit 828a5fb into main Jun 3, 2026
7 checks passed
@zknpr zknpr deleted the fix/json-patch-undo-rmw branch June 3, 2026 11:42
zknpr added a commit that referenced this pull request Jun 3, 2026
…ive state

reconcileRestoredDatabase replays forward or reverts saved-then-undone edits for
WASM (the saved file is the checkpoint state); native is a no-op (on-disk file is
already live). vscode-free, unit-tested against a real engine (incl. a json_patch
revert exercising the #427 undo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zknpr added a commit that referenced this pull request Jun 3, 2026
The docs/superpowers/ spec and plan files are internal brainstorming/implementation
working notes, not project documentation, and should not be tracked. Removes both
Part A's and the files inherited from #427; the design rationale lives in the PR
descriptions and commit messages.
zknpr added a commit that referenced this pull request Jun 3, 2026
The docs/superpowers/ spec and plan files (committed via #427) are internal
brainstorming/implementation working notes, not project documentation, and should
not be tracked. PR #434 likewise carries none.
zknpr added a commit that referenced this pull request Jun 3, 2026
…ive state

reconcileRestoredDatabase replays forward or reverts saved-then-undone edits for
WASM (the saved file is the checkpoint state); native is a no-op (on-disk file is
already live). vscode-free, unit-tested against a real engine (incl. a json_patch
revert exercising the #427 undo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zknpr added a commit that referenced this pull request Jun 3, 2026
#434)

Persist and reconstruct the exact undo/redo state across hot-exit and File>Revert — base, branch, and native cases — with per-operation-atomic reconcile and in-flight-save-safe checkpointing. Bundles the json_patch undo fidelity work (#427). Ships 1.5.1.

Closes #425.
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.

Hot-exit does not persist redo/undone state (futureStack) — undo of a saved edit is lost on reopen

1 participant