Skip to content

[feat] sync paired notebooks with open VS Code editors #4

Description

@TTTPOB

Goal

Keep a paired .ipynb and .py:percent file coherent when a developer edits or runs cells in an open VS Code Jupyter notebook and an agent edits the paired Python file. Preserve VS Code notebook editing, Jupyter execution, dirty state, undo history, outputs, and metadata.

Current boundary

jcli already converts .ipynb -> .py:percent, updates notebook sources after an agent edits .py, and detects source drift. Its agent post-hook currently writes .ipynb on disk. That write conflicts with a dirty VS Code notebook because VS Code keeps the unsaved in-memory model and later reports a changed-on-disk save conflict. VS Code has notebook diff but no notebook three-way merge editor.

Issue #2 documents a one-way Run On Save workflow. This issue supersedes that workflow when paired files need agent-to-notebook synchronization.

Proposed architecture

Ship a VS Code extension as the editor bridge. Keep jcli responsible for parsing, pairing, baselines, and merge decisions.

  1. On workspace.onDidSaveNotebookDocument, convert a paired jupyter-notebook file from .ipynb to .py:percent. Compare source-only content first so output-only saves do not rewrite the Python file.
  2. When an agent changes a paired .py, the jcli hook asks the bridge to synchronize. The bridge reads the open NotebookDocument as the notebook side of the merge.
  3. jcli returns a structured merge plan rather than writing the .ipynb directly. The bridge applies it through workspace.applyEdit() / WorkspaceEdit and NotebookEdit.
  4. For source-only changes to an existing cell, edit that cell's TextDocument to preserve its identity, outputs, execution state, selection, dirty state, and undo history. Use cell replacement only for inserts, deletes, and moves.
  5. The user performs a normal VS Code save after an in-memory merge. The bridge serializes merge jobs per notebook and recomputes when the notebook version or Python hash changes.
  6. If the notebook is closed, the hook can retain the existing disk update path. If it is open and dirty but the bridge is unavailable, the hook must not overwrite .ipynb.

Conflict handling

VS Code cannot host a native notebook three-way Merge Editor.

  • First implementation: expose base, notebook source, and incoming .py:percent as text for a standard three-way source merge; apply the chosen result back to the live notebook.
  • Later implementation: provide a cell-level conflict UI covering source, structural edits, metadata, and outputs.
  • Never write textual conflict markers into .ipynb.

Data model work in jcli

Add stable cell IDs to both representations and use them for merge/alignment. Preserve cell metadata, attachments, IDs, notebook metadata, outputs, and execution counts when applying source changes. Current index/source alignment cannot safely handle duplicate cells, moves, concurrent insertion, or full metadata round trips.

Execution behavior

The bridge must defer structural or source changes to a running cell until its execution completes. Outputs remain notebook-only and must not trigger .py rewrites. Source changes with preserved outputs need an explicit stale-output policy. j-cli exec must send output updates through the bridge for an open notebook, or avoid direct disk writes while its notebook is dirty.

Optional real-time phase

A local socket or WebSocket can carry structured operations between jcli and the extension. A Yjs-backed model may later provide concurrent editing semantics: Y.Array for cell order, Y.Text for source, and Y.Map for metadata. VS Code Extension APIs do not provide a shared Y.Doc or direct Monaco model binding, so the extension must translate Yjs transactions to TextEdit and NotebookEdit. This phase is optional; the saved-file and in-memory merge path must work without CRDT infrastructure.

Acceptance criteria

  • Saving a VS Code notebook updates the paired .py:percent without rewriting it for output-only changes.
  • An agent edit to .py updates an open dirty notebook in memory without a changed-on-disk conflict.
  • The user can undo the applied merge and save the notebook normally.
  • Closed notebooks retain a safe disk synchronization path.
  • Conflicts preserve both sides and offer a resolution path without corrupting .ipynb.
  • Tests cover dirty notebook source edits, output-only execution updates, concurrent cell structure edits, metadata/attachments preservation, save timing, watcher-loop prevention, and bridge-unavailable behavior.

Non-goals

  • Modify VS Code core or vscode-jupyter.
  • Replace the Jupyter notebook editor or kernel execution controller.
  • Depend on Live Share or JupyterLab RTC.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions