Skip to content

fix(data-editor): make edit replay robust across structural changes - #10662

Merged
Light2Dark merged 10 commits into
mainfrom
codex/fix-empty-data-editor-replay
Sep 2, 2026
Merged

fix(data-editor): make edit replay robust across structural changes#10662
Light2Dark merged 10 commits into
mainfrom
codex/fix-empty-data-editor-replay

Conversation

@Light2Dark

@Light2Dark Light2Dark commented Aug 26, 2026

Copy link
Copy Markdown
Member

This pull request was authored by a coding agent.

📝 Summary

Closes #10650

Deleting every row in a data editor could leave the frontend with no usable column metadata, so appending a row failed during replay. The same split replay paths also made structural edits vulnerable to stale column order, inconsistent conversion, and frontend/backend drift.

This change makes table structure explicit and independent of the cells that currently exist. The backend applies edits through one ordered table model that retains column names, row count, conversion examples, and dtype metadata across empty, sparse, scalar, row-oriented, and column-oriented inputs.

The frontend likewise routes both persisted replay and live cell, paste, row, and column operations through one pure editor-state transition. This removes the duplicate local mutation paths and ensures that the state shown to the user is produced by the same edit sequence sent to Python. Async CSV loads are committed through the loader's race guard and reconciled with the latest edit log, so stale requests cannot replace newer editor state.

Together, these changes fix the empty-table failure while making subsequent structural replay deterministic, type-safe, and easier to extend.

Fixed
image

📋 Pre-Review Checklist

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation and docstrings require no changes for this internal fix.
  • Tests cover empty-table replay, sequential structural edits, async loading races, schema conversion, and exact integer handling.

Written by GPT-5 on Codex Desktop

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
marimo-docs Ready Ready Preview Sep 2, 2026 4:47am UTC

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/_plugins/ui/_impl/test_data_editor.py">

<violation number="1" location="tests/_plugins/ui/_impl/test_data_editor.py:153">
P2: test_data_editor_replays_add_after_removing_all_rows constructs data_editor without the polars skipif that every other data_editor test uses (e.g. test_data_editor_initialization). In environments without polars, the identical construction is skipped in those tests, implying this one will fail. Add the same @pytest.mark.skipif(not DependencyManager.polars.has(), reason="Polars not installed") decorator.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant UI as Client / UI
    participant DE as DataEditor Instance
    participant TM as TableManager
    participant Engine as apply_edits logic

    Note over UI,Engine: Initialization
    UI->>DE: __init__(data, editable_columns, ...)
    DE->>TM: get_column_names()
    TM-->>DE: column_names
    DE->>DE: NEW: Store self._column_names

    Note over UI,Engine: Edit Replay (e.g., Remove then Add Row)
    UI->>DE: Trigger edit event (DataEdits)
    DE->>Engine: apply_edits(data, edits, column_names)
    
    Engine->>Engine: NEW: Resolve columns from column_names or schema
    Engine->>Engine: NEW: Capture original_values prototype from data[0]

    loop for each edit in DataEdits
        alt Row Removal
            Engine->>Engine: Remove row from data
            Note right of Engine: data may now be empty []
        else NEW: Positional/Column Edit
            Engine->>Engine: Check column_set for column_id
            opt column_id missing
                Engine->>Engine: Append to columns list
                Engine->>Engine: Update original_values prototype
            end
            
            alt rowIdx >= len(data)
                Engine->>Engine: CHANGED: Extend data using preserved column list
            end
            
            Engine->>DE: _convert_value(edit_value, prototype_value, dtype)
            DE-->>Engine: Casted value
            Engine->>Engine: Update data[row_idx][column_id]
        end
    end

    Engine-->>DE: Updated RowOrientedData
    DE-->>UI: Return result (schema preserved)
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread marimo/_plugins/ui/_impl/data_editor.py Outdated
Comment thread tests/_plugins/ui/_impl/test_data_editor.py
Comment thread marimo/_plugins/ui/_impl/data_editor.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread marimo/_plugins/ui/_impl/data_editor.py Outdated
Comment thread marimo/_plugins/ui/_impl/data_editor.py Outdated
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for ./frontend

Status Category Percentage Covered / Total
🔵 Lines 56.6% 21102 / 37278
🔵 Statements 56.54% 21470 / 37969
🔵 Functions 49.68% 4746 / 9552
🔵 Branches 49.86% 10869 / 21799
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
frontend/src/plugins/impl/DataEditorPlugin.tsx 77.27% 68.75% 62.5% 80.95% 114-120, 138-141
frontend/src/plugins/impl/data-editor/data-utils.ts 82.69% 87.17% 100% 82.35% 19, 41, 98-99, 122, 126-127, 144-145
frontend/src/plugins/impl/data-editor/editor-state.ts 83.78% 81.25% 100% 83.78% 34, 51, 58, 74, 91, 103
frontend/src/plugins/impl/data-editor/glide-data-editor.tsx 39.51% 20.48% 25.92% 40.16% 107-142, 159, 168-171, 177-181, 190-219, 231-239, 245-246, 260-272, 276, 280-292, 296-304, 309-311, 320-341, 369-393, 414
frontend/src/plugins/impl/data-editor/glide-utils.ts 67.77% 54.76% 100% 67.41% 25-37, 47-60, 73, 82-95, 146, 153, 160, 165, 199-204, 219-220
frontend/src/plugins/impl/data-editor/types.ts 100% 100% 100% 100%
Generated in workflow #20942 for commit 33c5777 by the Vitest Coverage Report Action

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 issues found across 12 files (changes from recent commits).

You’re at about 90% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="marimo/_plugins/ui/_impl/data_editor.py">

<violation number="1" location="marimo/_plugins/ui/_impl/data_editor.py:170">
P2: When a sparse column's only representative value falls outside the ten sampled rows, replay loses its inferred type after the rows are removed. Derive conversion metadata from every column's non-null values, or preserve representative metadata before sampling, so post-deletion edits keep the original type.</violation>

<violation number="2" location="marimo/_plugins/ui/_impl/data_editor.py:170">
P2: When the only non-null exemplar falls outside the ten sampled rows, editing a null cell in an untyped column returns the raw string instead of preserving the column's value type. Infer conversion values across all rows, or otherwise guarantee that each column's representative is found.</violation>
</file>

<file name="frontend/src/plugins/impl/DataEditorPlugin.tsx">

<violation number="1" location="frontend/src/plugins/impl/DataEditorPlugin.tsx:50">
P2: Adding `columnNames` here makes the committed plugin OpenAPI schema stale. Regenerate and commit `frontend/plugins.openapi.yaml` so the plugin-schema synchronization test and published contract include this field.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread marimo/_plugins/ui/_impl/data_editor.py
Comment thread frontend/src/plugins/impl/DataEditorPlugin.tsx
Comment thread marimo/_plugins/ui/_impl/data_editor.py
@Light2Dark
Light2Dark marked this pull request as ready for review August 26, 2026 16:18
@Light2Dark
Light2Dark marked this pull request as draft August 26, 2026 16:38
@Light2Dark Light2Dark changed the title fix(data-editor): preserve columns after deleting every row fix(data-editor): make edit replay robust across structural changes Aug 26, 2026
@Light2Dark
Light2Dark marked this pull request as ready for review August 26, 2026 17:40
@Light2Dark

Copy link
Copy Markdown
Member Author

Tested manually and is working

@kirangadhave
kirangadhave requested a lite review from Copilot August 27, 2026 18:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes data-editor edit replay failures when the table becomes empty and improves determinism for structural edits by making table structure (column names/order, row count, dtype hints) independent of currently materialized cells on both backend and frontend.

Changes:

  • Backend: refactors edit application through a single _EditableTable model, adds scalar-list support, and preserves schema/column metadata across empty/sparse inputs.
  • Frontend: routes replay + live edits through a single pure applyEditorEdits transition and threads explicit columnNames through plugin state to stabilize column ordering.
  • Tests/OpenAPI: adds comprehensive regression coverage (empty replay, structural sequencing, precision handling) and exposes columnNames in the plugin schema.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/_plugins/ui/_impl/test_data_editor.py Adds regression tests for empty-table appends, structural replay, conversion rules, and integer precision.
marimo/_plugins/ui/_impl/data_editor.py Introduces _EditableTable-based edit application, scalar-list handling, and improved conversion semantics.
frontend/src/plugins/impl/DataEditorPlugin.tsx Switches to editor-state-driven replay and passes explicit columnNames into the editor.
frontend/src/plugins/impl/data-editor/types.ts Refines edit types and introduces EditorState/EditorRow types.
frontend/src/plugins/impl/data-editor/glide-utils.ts Adds isValidCellValue and removes local mutation from paste (edits-only pathway).
frontend/src/plugins/impl/data-editor/glide-data-editor.tsx Removes duplicated local replay/mutation and emits normalized edits for all operations.
frontend/src/plugins/impl/data-editor/editor-state.ts New pure reducer-style state transition for applying edit logs.
frontend/src/plugins/impl/data-editor/data-utils.ts Adds orderColumnFields, updates column operations to be name-/index-aware and preserve order.
frontend/src/plugins/impl/data-editor/tests/glide-utils.test.ts Adds validation tests and updates paste behavior expectations.
frontend/src/plugins/impl/data-editor/tests/glide-data-editor.test.tsx Updates editor tests to assert emitted edits instead of local mutation.
frontend/src/plugins/impl/data-editor/tests/editor-state.test.ts New tests for deterministic replay and immutability of applyEditorEdits.
frontend/src/plugins/impl/data-editor/tests/data-utils.test.ts Extends tests for ordering and updated column operations semantics.
frontend/src/plugins/impl/tests/DataEditorPlugin.test.ts Adds async-load race tests to ensure latest edits win and stale loads are ignored.
frontend/plugins.openapi.yaml Adds columnNames to the plugin schema.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread frontend/src/plugins/impl/DataEditorPlugin.tsx Outdated
Comment thread frontend/src/plugins/impl/data-editor/glide-utils.ts Outdated
Comment thread frontend/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts Outdated
Comment thread frontend/src/plugins/impl/data-editor/editor-state.ts
Comment thread frontend/src/plugins/impl/data-editor/data-utils.ts Outdated
Comment thread frontend/src/plugins/impl/data-editor/data-utils.ts Outdated
Comment thread frontend/src/plugins/impl/data-editor/data-utils.ts Outdated
Comment thread frontend/src/plugins/impl/data-editor/data-utils.ts Outdated
Comment thread frontend/src/plugins/impl/__tests__/DataEditorPlugin.test.ts Outdated
@Light2Dark
Light2Dark merged commit 8869b18 into main Sep 2, 2026
49 checks passed
@Light2Dark
Light2Dark deleted the codex/fix-empty-data-editor-replay branch September 2, 2026 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

data_editor: IndexError appending a row to an empty row-oriented editor

3 participants