From 86c813ec03e340ffbe4aeb97cd0c5bee7a0ead94 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:12:04 -0400 Subject: [PATCH] fix(notebooks): reset insert offset when anchor cell moves (#49694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Bug fix ## What is the current behavior? When a cell gets moved via `move_cell` operation in `deriveNotebookDiff`, the `insertedAfter` offset map is not cleared for that anchor cell. This causes later `insert_cell` operations anchored on the same (now-moved) cell to apply the stale offset on top of the correct current-position lookup, resulting in the new cell landing after the wrong position. ## What is the new behavior? The offset for an anchor cell is now cleared from `insertedAfter` when it gets moved, since cells previously inserted after it stay behind at its old location and should not affect subsequent inserts at its new position. A regression test has been added that reproduces the exact ticket scenario (insert after cell-1, move cell-1 after cell-3, insert after cell-1 again) and verifies the correct final cell order. ## Additional context Fixes: https://linear.app/supabase/issue/FE-4308/insert-anchored-to-a-previously-moved-cell-lands-after-the-wrong-cell ## Summary by CodeRabbit * **Bug Fixes** * Fixed notebook cell insertions after moving an anchor cell, ensuring new inserts appear relative to the anchor’s updated position. * Preserved the placement of inserts made before the anchor cell was moved. --- .../notebooks/notebook-operations.test.ts | 35 +++++++++++++++++++ .../content/notebooks/notebook-operations.ts | 3 ++ 2 files changed, 38 insertions(+) diff --git a/apps/studio/data/content/notebooks/notebook-operations.test.ts b/apps/studio/data/content/notebooks/notebook-operations.test.ts index 77a5ba0a804..1194884333a 100644 --- a/apps/studio/data/content/notebooks/notebook-operations.test.ts +++ b/apps/studio/data/content/notebooks/notebook-operations.test.ts @@ -121,6 +121,41 @@ describe('applyNotebookOperations', () => { ]) }) + it('resets the insert offset for an anchor cell once it gets moved', () => { + // cell-1 gets an insert right after it, then cell-1 itself moves after cell-3. A later + // insert anchored on cell-1 must land right after its *new* position — the first insert + // stayed behind at cell-1's old spot, so it shouldn't count toward this offset anymore. + const notebook: NotebookWire = { + schema_version: 1, + cells: [ + { _tag: 'markdown_cell', _id: 'cell-1', text: '1' }, + { _tag: 'markdown_cell', _id: 'cell-2', text: '2' }, + { _tag: 'markdown_cell', _id: 'cell-3', text: '3' }, + { _tag: 'markdown_cell', _id: 'cell-4', text: '4' }, + ], + } + const ops: NotebookOperation[] = [ + { + _tag: 'insert_cell', + after_cell_id: 'cell-1', + cell: { _tag: 'markdown_cell', text: 'first' }, + }, + { _tag: 'move_cell', cell_id: 'cell-1', after_cell_id: 'cell-3' }, + { + _tag: 'insert_cell', + after_cell_id: 'cell-1', + cell: { _tag: 'markdown_cell', text: 'second' }, + }, + ] + + const result = applyNotebookOperations(notebook, ops) + + expect(result.success).toBe(true) + if (!result.success) return + const texts = result.notebook.cells.map((cell) => ('text' in cell ? cell.text : undefined)) + expect(texts).toEqual(['first', '2', '3', '1', 'second', '4']) + }) + it('resolves a move anchored on another moved cell using its new position', () => { // cell-1 moves after cell-3 first, landing at [cell-2, cell-3, cell-1]; cell-2 then moves // after cell-1's *new* position, giving [cell-3, cell-1, cell-2]. diff --git a/apps/studio/data/content/notebooks/notebook-operations.ts b/apps/studio/data/content/notebooks/notebook-operations.ts index 60d2dcb27d2..2a2b0404de4 100644 --- a/apps/studio/data/content/notebooks/notebook-operations.ts +++ b/apps/studio/data/content/notebooks/notebook-operations.ts @@ -256,6 +256,9 @@ export function deriveNotebookDiff( } entries.splice(found.index, 1) + // Cells already inserted after this anchor stay behind at its old position, so a + // later insert anchored on it should start counting from its new position again. + insertedAfter.delete(operation.cell_id) const error = insertAfter(operation.after_cell_id, { _tag: 'moved', cell: found.cell,