Skip to content

fix: Fix deepnote notebook deserializer and file change watcher#363

Draft
tkislan wants to merge 5 commits intomainfrom
tk/fix-project-notebook-picker
Draft

fix: Fix deepnote notebook deserializer and file change watcher#363
tkislan wants to merge 5 commits intomainfrom
tk/fix-project-notebook-picker

Conversation

@tkislan
Copy link
Contributor

@tkislan tkislan commented Mar 18, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Notebook selection now respects saved notebook choices before using the active editor, fixing incorrect selection in some cases.
    • Rapid switching between notebooks now reliably reflects the latest selection.
  • Improvements

    • Faster, more reliable save flow with immediate in-memory updates to the UI and more consistent change detection.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

findCurrentNotebookId now prefers a stored notebook selection from the notebook manager, falls back to the active notebook editor if no stored selection exists, and finally searches open Deepnote notebook documents by project ID. deepnoteFileChangeWatcher normalizes URIs for deterministic self-write tracking, applies WorkspaceEdits immediately in-memory, writes serialized notebook bytes directly to disk via workspace.fs.writeFile, then calls workspace.save to clear dirty state; self-write marking/consumption was adjusted and serialization/write/save errors are logged. Unit tests were updated and expanded to cover selection priority and rapid switching scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant UI as "User"
    participant Manager as "NotebookManager"
    participant Editor as "ActiveEditor"
    participant Docs as "OpenDocuments"

    UI->>Manager: request current notebook id
    alt Manager has stored selection
        Manager-->>UI: return stored selection
    else no stored selection
        Manager->>Editor: query active editor notebook
        alt Editor is deepnote with notebookId
            Editor-->>UI: return editor notebook id
        else
            Editor->>Docs: search open Deepnote docs by projectId
            Docs-->>UI: return matching notebook id or null
        end
    end
Loading
sequenceDiagram
    participant Caller as "Serializer.executeMainFileSync"
    participant Workspace as "vscode.workspace"
    participant FS as "vscode.workspace.fs"
    participant Watcher as "FileChangeWatcher"

    Caller->>Workspace: applyEdit(WorkspaceEdit)  -- in-memory update
    Caller->>Serializer: serialize notebook -> bytes
    Caller->>FS: writeFile(fileUri, bytes)  -- direct disk write
    FS-->>Watcher: file change event (normalized URI)
    Caller->>Workspace: save(notebook.uri)  -- clear dirty state / update mtime
    Note right of Watcher: consumeSelfWrite(normalizeFileUri(uri))
    Watcher-->>Caller: ignore self-write if matched
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Updates Docs ⚠️ Warning PR modifies documented findCurrentNotebookId() function behavior without updating specs/architecture.md documentation. Update specs/architecture.md to reflect new priority: stored notebook selection before active editor fallback.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately reflects the main changes: fixes to deserializer (reordered notebook selection priority) and file watcher (URI normalization and sync flow improvements).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

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

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 18, 2026
@tkislan tkislan marked this pull request as ready for review March 19, 2026 07:51
@tkislan tkislan requested a review from a team as a code owner March 19, 2026 07:51
@codecov
Copy link

codecov bot commented Mar 19, 2026

❌ 4 Tests Failed:

Tests completed Failed Passed Skipped
2195 4 2191 189
View the full list of 4 ❄️ flaky test(s)
should not suppress real changes after auto-save::DeepnoteFileChangeWatcher should not suppress real changes after auto-save

Flake rate in main: 6.67% (Passed 14 times, Failed 1 times)

Stack Traces | 5s run time
Timeout of 5000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.js)
should reload dirty notebooks::DeepnoteFileChangeWatcher should reload dirty notebooks

Flake rate in main: 6.67% (Passed 14 times, Failed 1 times)

Stack Traces | 2s run time
Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.js)
should reload on external change::DeepnoteFileChangeWatcher should reload on external change

Flake rate in main: 6.67% (Passed 14 times, Failed 1 times)

Stack Traces | 2s run time
Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.js)
should use atomic edit (single applyEdit for replaceCells + metadata)::DeepnoteFileChangeWatcher should use atomic edit (single applyEdit for replaceCells + metadata)

Flake rate in main: 6.67% (Passed 14 times, Failed 1 times)

Stack Traces | 2s run time
Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (.../notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.js)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@tkislan tkislan changed the title fix: Fix deepnote notebook deserializer fix: Fix deepnote notebook deserializer and file change watcher Mar 19, 2026
@tkislan tkislan marked this pull request as draft March 19, 2026 14:01
Copy link
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/notebooks/deepnote/deepnoteFileChangeWatcher.ts`:
- Around line 356-373: The writeFile catch path allows execution to continue to
the subsequent markSelfWrite and workspace.save calls which assume the file was
written; modify the write failure handler in deepnoteFileChangeWatcher (around
workspace.fs.writeFile) to return early after calling
this.consumeSelfWrite(fileUri) and logging the warning so we do not proceed to
markSelfWrite or call workspace.save on a failed write; ensure markSelfWrite and
consumeSelfWrite usage remains correct and only the successful-write path
reaches the save logic.
🪄 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: aacbc659-5d97-4eeb-9ad4-a231351e6a81

📥 Commits

Reviewing files that changed from the base of the PR and between 2de7885 and 9b5c107.

📒 Files selected for processing (1)
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.ts

Comment on lines +356 to +373
this.markSelfWrite(fileUri);
try {
await workspace.fs.writeFile(fileUri, serializedBytes);
} catch (writeError) {
this.consumeSelfWrite(fileUri);
logger.warn(`[FileChangeWatcher] Failed to write synced file: ${fileUri.path}`, writeError);
}

// Now save — VS Code serializes (same bytes), sees the mtime is from our
// recent write (which its internal watcher has picked up), and writes
// successfully without a "content is newer" conflict.
this.markSelfWrite(fileUri);
try {
await workspace.save(notebook.uri);
} catch (saveError) {
this.consumeSelfWrite(fileUri);
logger.warn(`[FileChangeWatcher] Save after sync write failed: ${notebook.uri.path}`, saveError);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing return after writeFile failure may cause unintended behavior.

If writeFile fails (line 358), execution continues to markSelfWrite and workspace.save (lines 367-373). The comment at line 364-366 assumes the file was written ("mtime is from our recent write"), which doesn't hold on failure.

Consider returning early after a writeFile failure, or clarify if the fallback to save is intentional.

Proposed fix
             try {
                 await workspace.fs.writeFile(fileUri, serializedBytes);
             } catch (writeError) {
                 this.consumeSelfWrite(fileUri);
                 logger.warn(`[FileChangeWatcher] Failed to write synced file: ${fileUri.path}`, writeError);
+                return;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.markSelfWrite(fileUri);
try {
await workspace.fs.writeFile(fileUri, serializedBytes);
} catch (writeError) {
this.consumeSelfWrite(fileUri);
logger.warn(`[FileChangeWatcher] Failed to write synced file: ${fileUri.path}`, writeError);
}
// Now save — VS Code serializes (same bytes), sees the mtime is from our
// recent write (which its internal watcher has picked up), and writes
// successfully without a "content is newer" conflict.
this.markSelfWrite(fileUri);
try {
await workspace.save(notebook.uri);
} catch (saveError) {
this.consumeSelfWrite(fileUri);
logger.warn(`[FileChangeWatcher] Save after sync write failed: ${notebook.uri.path}`, saveError);
}
this.markSelfWrite(fileUri);
try {
await workspace.fs.writeFile(fileUri, serializedBytes);
} catch (writeError) {
this.consumeSelfWrite(fileUri);
logger.warn(`[FileChangeWatcher] Failed to write synced file: ${fileUri.path}`, writeError);
return;
}
// Now save — VS Code serializes (same bytes), sees the mtime is from our
// recent write (which its internal watcher has picked up), and writes
// successfully without a "content is newer" conflict.
this.markSelfWrite(fileUri);
try {
await workspace.save(notebook.uri);
} catch (saveError) {
this.consumeSelfWrite(fileUri);
logger.warn(`[FileChangeWatcher] Save after sync write failed: ${notebook.uri.path}`, saveError);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteFileChangeWatcher.ts` around lines 356 - 373,
The writeFile catch path allows execution to continue to the subsequent
markSelfWrite and workspace.save calls which assume the file was written; modify
the write failure handler in deepnoteFileChangeWatcher (around
workspace.fs.writeFile) to return early after calling
this.consumeSelfWrite(fileUri) and logging the warning so we do not proceed to
markSelfWrite or call workspace.save on a failed write; ensure markSelfWrite and
consumeSelfWrite usage remains correct and only the successful-write path
reaches the save logic.

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