Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6802960
fix: Fix deepnote notebook deserializer
tkislan Mar 18, 2026
2de7885
Add tests
tkislan Mar 18, 2026
bc2de06
Merge branch 'main' into tk/fix-project-notebook-picker
tkislan Mar 19, 2026
3a418c1
Fix file change watcher
tkislan Mar 19, 2026
9b5c107
Merge remote-tracking branch 'origin/main' into tk/fix-project-notebo…
tkislan Mar 19, 2026
77c0347
Improve error handling in DeepnoteFileChangeWatcher to prevent stale …
tkislan Mar 20, 2026
6dc77a0
Add unit tests for DeepnoteFileChangeWatcher to handle scenarios with…
tkislan Mar 20, 2026
8898be9
Format code
tkislan Mar 20, 2026
c74ffa7
Enhance error handling in DeepnoteFileChangeWatcher to check save ope…
tkislan Mar 23, 2026
8d061ac
Add post-snapshot read grace period in unit tests for DeepnoteFileCha…
tkislan Mar 23, 2026
5388ead
Remove the second markSelfWrite() on the workspace.save() path.
tkislan Mar 26, 2026
6d05f22
Merge branch 'main' into tk/fix-project-notebook-picker
tkislan Mar 26, 2026
c5342a3
Merge branch 'main' into tk/fix-project-notebook-picker
tkislan Mar 26, 2026
3e7dfc9
feat(deepnote): Add clearNotebookSelection method and update notebook…
tkislan Mar 27, 2026
c160ae3
Update test
tkislan Mar 27, 2026
0b2504a
refactor(deepnote): Improve mock child process and server output mana…
tkislan Mar 27, 2026
9d5c6c0
refactor(deepnote): Simplify notebook edit application logic
tkislan Mar 27, 2026
44bd482
feat(deepnote): Enhance notebook resolution management and error hand…
tkislan Mar 30, 2026
36cc1e6
refactor(deepnote): Improve notebook ID retrieval with zod validation
tkislan Mar 31, 2026
71579cf
refactor(deepnote): Improve variable naming and import organization i…
tkislan Mar 31, 2026
23fdcca
Fix notebook deserialization race conditions
tkislan Mar 31, 2026
824f471
feat(deepnote): Enhance testing for DeepnoteFileChangeWatcher and Dee…
tkislan Mar 31, 2026
c6d21e6
feat(deepnote): Add snapshot interaction tests for DeepnoteFileChange…
tkislan Apr 1, 2026
5d9d6e2
Fix cspell
tkislan Apr 1, 2026
4962616
Fix tests
tkislan Apr 1, 2026
552366b
Minor improvements
tkislan Apr 1, 2026
bd4d5fd
fix(deepnote): Enhance error handling for metadata restoration in Dee…
tkislan Apr 1, 2026
cb4644f
Merge remote-tracking branch 'origin/main' into tk/fix-project-notebo…
tkislan Apr 2, 2026
ec96f53
feat(deepnote): Add DeepnoteNotebookInfoStatusBar for displaying note…
tkislan Apr 2, 2026
d44c1aa
feat(deepnote): Add command to copy active notebook details
tkislan Apr 2, 2026
7815f42
Add a failing test for external change rerender
tkislan Apr 2, 2026
855f38c
Refactor self write mark handling
tkislan Apr 2, 2026
f72b0fa
feat(deepnote): Enhance notebook ID resolution with tab-based logic
tkislan Apr 2, 2026
8e6f679
feat(deepnote): Refactor notebook selection handling and enhance mism…
tkislan Apr 7, 2026
0396a2c
refactor(deepnote): Simplify DeepnoteActivationService and enhance no…
tkislan Apr 7, 2026
06d3197
refactor(deepnote): Streamline DeepnoteNotebookSerializer and remove …
tkislan Apr 7, 2026
f4b79d3
feat(deepnote): Add logging for pending notebook resolutions in Deepn…
tkislan Apr 7, 2026
c83af55
Reformat code
tkislan Apr 7, 2026
40f6c67
Fix test
tkislan Apr 7, 2026
46cbcda
feat(deepnote): Enhance notebook deserialization and verification logic
tkislan Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/mocha-esm-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export async function load(url, context, nextLoad) {
export const EndOfLine = createClassProxy('EndOfLine');
export const PortAutoForwardAction = createClassProxy('PortAutoForwardAction');
export const PortAttributes = createClassProxy('PortAttributes');
export const TabInputNotebook = createClassProxy('TabInputNotebook');
`,
shortCircuit: true
};
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
"category": "Deepnote",
"icon": "$(reveal)"
},
{
"command": "deepnote.copyNotebookDetails",
"title": "%deepnote.commands.copyNotebookDetails.title%",
"category": "Deepnote",
"icon": "$(copy)"
},
Comment on lines +96 to +101
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find and examine package.json around the command definition and menus section
fd -t f "package.json" | head -1 | xargs wc -l

Repository: deepnote/vscode-deepnote

Length of output: 87


🏁 Script executed:

# Look for the command definition and any menus sections
rg -n "deepnote.copyNotebookDetails" -A 5 -B 5

Repository: deepnote/vscode-deepnote

Length of output: 1794


🏁 Script executed:

# Check if there are any menus.commandPalette entries for this command
rg -n "commandPalette" -A 10 -B 2

Repository: deepnote/vscode-deepnote

Length of output: 6998


🏁 Script executed:

# Find and examine the handler implementation
fd -t f "deepnoteNotebookInfoStatusBar.ts" | head -1

Repository: deepnote/vscode-deepnote

Length of output: 123


🏁 Script executed:

# Search more specifically for commandPalette entries related to copyNotebookDetails
rg -n "copyNotebookDetails" -A 3 -B 1

Repository: deepnote/vscode-deepnote

Length of output: 1409


🏁 Script executed:

# Get the full menus.commandPalette section to verify if copyNotebookDetails is listed
sed -n '1202,1250p' package.json

Repository: deepnote/vscode-deepnote

Length of output: 2412


🏁 Script executed:

# Now examine the handler implementation
cat -n src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.ts | head -100

Repository: deepnote/vscode-deepnote

Length of output: 3805


🏁 Script executed:

# Check if there's a context variable for active Deepnote notebooks
rg -n "deepnote.isdeepnotenotebook|deepnote.isactive" package.json

Repository: deepnote/vscode-deepnote

Length of output: 50


🏁 Script executed:

# Look for similar commands that are scoped to Deepnote notebook context
rg -n "deepnote.enable|deepnote.disable" -A 3 package.json | head -40

Repository: deepnote/vscode-deepnote

Length of output: 2138


🏁 Script executed:

# Check the full handler to see if it validates for Deepnote notebook type
sed -n '77,110p' src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.ts

Repository: deepnote/vscode-deepnote

Length of output: 1183


🏁 Script executed:

# Check if enableSnapshots and disableSnapshots have commandPalette entries with scoping
rg -n "enableSnapshots|disableSnapshots" package.json | grep -A 3 "command"

Repository: deepnote/vscode-deepnote

Length of output: 335


🏁 Script executed:

# Search for all commandPalette entries to see if any use "deepnote.isdeepnotenotebook" or similar context
rg -n '"when"' package.json | grep -i deepnote | head -20

Repository: deepnote/vscode-deepnote

Length of output: 2223


🏁 Script executed:

# Check revealInExplorer command for comparison
rg -n "revealInExplorer" package.json -A 4 -B 1

Repository: deepnote/vscode-deepnote

Length of output: 601


Add commandPalette scoping to gate this command to Deepnote notebooks.

The command is globally discoverable in the palette but the handler silently returns when no active Deepnote notebook exists, creating a dead command outside that context. Add "when": "notebookType == 'deepnote'" to the command definition, following the pattern used elsewhere in the manifest.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 96 - 101, The manifest exposes the command
"deepnote.copyNotebookDetails" globally but its handler only works for Deepnote
notebooks; update the command entry for "deepnote.copyNotebookDetails" to
include the scope gating used elsewhere by adding a when clause of "notebookType
== 'deepnote'". Locate the command object with "command":
"deepnote.copyNotebookDetails" and add the property "when": "notebookType ==
'deepnote'" so the command is only shown in the command palette for Deepnote
notebooks.

{
"command": "deepnote.enableSnapshots",
"title": "%deepnote.commands.enableSnapshots.title%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@
"deepnote.commands.openNotebook.title": "Open Notebook",
"deepnote.commands.openFile.title": "Open File",
"deepnote.commands.revealInExplorer.title": "Reveal in Explorer",
"deepnote.commands.copyNotebookDetails.title": "Copy Active Deepnote Notebook Details",
"deepnote.commands.enableSnapshots.title": "Enable Snapshots",
"deepnote.commands.disableSnapshots.title": "Disable Snapshots",
"deepnote.commands.manageIntegrations.title": "Manage Integrations",
Expand Down
12 changes: 11 additions & 1 deletion src/kernels/deepnote/deepnoteServerStarter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension

throw new DeepnoteServerStartupError(
interpreter.uri.fsPath,
serverInfo?.jupyterPort ?? 0,
0,
'unknown',
capturedOutput?.stdout || '',
capturedOutput?.stderr || '',
Expand Down Expand Up @@ -402,6 +402,16 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
*/
private monitorServerOutput(fileKey: string, serverInfo: DeepnoteServerInfo): void {
const proc = serverInfo.process;
const existing = this.disposablesByFile.get(fileKey);
if (existing) {
for (const d of existing) {
try {
d.dispose();
} catch (ex) {
logger.warn(`Error disposing listener for ${fileKey}`, ex);
}
}
}
const disposables: IDisposable[] = [];
this.disposablesByFile.set(fileKey, disposables);

Expand Down
59 changes: 57 additions & 2 deletions src/kernels/deepnote/deepnoteTestHelpers.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,66 @@ import type { ChildProcess } from 'node:child_process';
* Satisfies the ChildProcess interface with minimal stub values.
*/
export function createMockChildProcess(overrides?: Partial<ChildProcess>): ChildProcess {
return {
const mockProcess: ChildProcess = {
pid: undefined,
stdio: [null, null, null, null, null],
stdin: null,
stdout: null,
stderr: null,
exitCode: null,
killed: false,
connected: false,
signalCode: null,
spawnargs: [],
spawnfile: '',
kill: () => true,
send: () => true,
disconnect: () => true,
unref: () => true,
ref: () => true,
addListener: function () {
return this;
},
emit: () => true,
on: function () {
return this;
},
once: function () {
return this;
},
removeListener: function () {
return this;
},
removeAllListeners: function () {
return this;
},
prependListener: function () {
return this;
},
prependOnceListener: function () {
return this;
},
[Symbol.dispose]: () => {
return undefined;
},
off: function () {
return this;
},
setMaxListeners: function () {
return this;
},
getMaxListeners: () => 10,
listeners: function () {
return [];
},
rawListeners: function () {
return [];
},
eventNames: function () {
return [];
},
listenerCount: () => 0,
...overrides
} as ChildProcess;
};
return mockProcess;
}
7 changes: 6 additions & 1 deletion src/notebooks/deepnote/deepnoteActivationService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { inject, injectable, optional } from 'inversify';
import { commands, l10n, workspace, window, type Disposable, type NotebookDocumentContentOptions } from 'vscode';
import { commands, l10n, window, workspace, type Disposable, type NotebookDocumentContentOptions } from 'vscode';

import { IExtensionSyncActivationService } from '../../platform/activation/types';
import { IExtensionContext } from '../../platform/common/types';
Expand Down Expand Up @@ -51,6 +51,11 @@ export class DeepnoteActivationService implements IExtensionSyncActivationServic

this.registerSerializer();
this.extensionContext.subscriptions.push(this.editProtection);
this.extensionContext.subscriptions.push(
workspace.onDidOpenNotebookDocument((doc) => {
void this.serializer.verifyDeserializedNotebook(doc);
})
);
Comment on lines +54 to +58
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.

🧹 Nitpick | 🔵 Trivial

Fire-and-forget async calls may overlap.

Rapid notebook opens trigger concurrent verifyDeserializedNotebook calls. Each reads file, deserializes, and applies edits independently. Works correctly due to VS Code's edit serialization, but consider whether queuing/debouncing would reduce unnecessary I/O.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteActivationService.ts` around lines 54 - 58,
The onDidOpenNotebookDocument handler currently fire-and-forgets
serializer.verifyDeserializedNotebook(doc), which can overlap under rapid opens;
change this to serialize or debounce calls per notebook URI: implement a
lightweight per-document queue or a Map<string, Promise> (keyed by
doc.uri.toString()) that stores the last pending verifyDeserializedNotebook
promise and chains the next call onto it (or use a short debounce timer keyed by
URI to coalesce rapid opens) so each verifyDeserializedNotebook invocation waits
for the prior one for the same notebook to finish before
reading/deserializing/applying edits; update the handler in the subscription
registered around workspace.onDidOpenNotebookDocument to use that
queuing/debouncing mechanism and clear Map entries when done or when errors
occur.

this.extensionContext.subscriptions.push(
workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration('deepnote.snapshots.enabled')) {
Expand Down
12 changes: 8 additions & 4 deletions src/notebooks/deepnote/deepnoteExplorerView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class DeepnoteExplorerView {
await this.treeDataProvider.refreshNotebook(treeItem.context.projectId);

// Optionally open the duplicated notebook
this.manager.selectNotebookForProject(treeItem.context.projectId, newNotebook.id);
this.registerNotebookOpenIntent(treeItem.context.projectId, newNotebook.id);
const notebookUri = fileUri.with({ query: `notebook=${newNotebook.id}` });
const document = await workspace.openNotebookDocument(notebookUri);
await window.showNotebookDocument(document, {
Expand Down Expand Up @@ -508,7 +508,7 @@ export class DeepnoteExplorerView {
await this.treeDataProvider.refreshNotebook(projectData.project.id);

// Open the new notebook
this.manager.selectNotebookForProject(projectData.project.id, notebookId);
this.registerNotebookOpenIntent(projectData.project.id, notebookId);
const notebookUri = fileUri.with({ query: `notebook=${notebookId}` });
const document = await workspace.openNotebookDocument(notebookUri);
await window.showNotebookDocument(document, {
Expand All @@ -517,6 +517,10 @@ export class DeepnoteExplorerView {
});
}

private registerNotebookOpenIntent(projectId: string, notebookId: string): void {
this.manager.queueNotebookResolution(projectId, notebookId);
}

private refreshExplorer(): void {
this.treeDataProvider.refresh();
}
Expand All @@ -537,7 +541,7 @@ export class DeepnoteExplorerView {

console.log(`Selecting notebook in manager.`);

this.manager.selectNotebookForProject(context.projectId, context.notebookId);
this.registerNotebookOpenIntent(context.projectId, context.notebookId);

console.log(`Opening notebook document.`, fileUri);

Expand Down Expand Up @@ -701,7 +705,7 @@ export class DeepnoteExplorerView {

this.treeDataProvider.refresh();

this.manager.selectNotebookForProject(projectId, notebookId);
this.registerNotebookOpenIntent(projectId, notebookId);

const notebookUri = fileUri.with({ query: `notebook=${notebookId}` });
const document = await workspace.openNotebookDocument(notebookUri);
Expand Down
Loading
Loading