Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f0de6cd
refactor(deepnote): re-key notebook manager per (projectId, notebookI…
tkislan Jun 23, 2026
005accf
refactor(deepnote): render single notebook + serialize by metadata, d…
tkislan Jun 23, 2026
0ee7f53
feat(deepnote): split legacy multi-notebook files into single-noteboo…
tkislan Jun 23, 2026
8e27889
feat(deepnote): propagate project metadata across sibling files on disk
tkislan Jun 23, 2026
3995237
feat(deepnote): group the explorer by project and add a notebook stat…
tkislan Jun 24, 2026
3e16e20
refactor(deepnote): key the Jupyter server and environment per notebook
tkislan Jun 24, 2026
83d4e91
feat(deepnote): scope snapshots per notebook with a backward-compatib…
tkislan Jun 24, 2026
faa8432
feat(deepnote): run the init notebook per kernel from its sibling file
tkislan Jun 24, 2026
4a43a7d
fix(deepnote): satisfy lint and spell-check on the migration branch
tkislan Jun 24, 2026
f92d173
test(deepnote): use the real @deepnote/convert in unit tests instead …
tkislan Jun 24, 2026
6532d96
fix(deepnote): address code-review findings on the snapshot/serialize…
tkislan Jun 24, 2026
6d46725
fix(deepnote): exact snapshot lookup in the file watcher + close-canc…
tkislan Jun 26, 2026
21611db
refactor(deepnote): remove metadata propagation and project-level exp…
tkislan Jun 30, 2026
7814c6b
Merge origin/main into tk/single-notebook
tkislan Jun 30, 2026
663d31d
Revert unnecessary renamings
tkislan Jun 30, 2026
1643ef5
fix(deepnote): trim indented bullet content before stripping markdown
tkislan Jun 30, 2026
be64263
refactor(deepnote): import convert helpers directly, drop snapshotFil…
tkislan Jun 30, 2026
589845d
refactor(deepnote): update buildSnapshotPath to use object destructuring
tkislan Jun 30, 2026
3a910c1
test(deepnote): remove redundant subagent-written tests
tkislan Jun 30, 2026
dc0b2dd
Merge remote-tracking branch 'origin/main' into tk/single-notebook
tkislan Jul 1, 2026
ee38cf6
feat(deepnote): retire the split-away original as a .legacy backup
tkislan Jul 1, 2026
8c341e0
test(e2e): add multi-notebook split test with per-spec screenshots
tkislan Jul 1, 2026
c0cb36a
test(e2e): add single-notebook-open and init-notebook split/runner su…
tkislan Jul 1, 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ vscode.d.ts
vscode.proposed.*.d.ts
xunit-test-results.xml
tsconfig.tsbuildinfo
/testing

# ExTester (vscode-extension-tester) E2E artifacts
test-resources
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"${workspaceFolder}/dist/**/*",
"!${workspaceFolder}/**/node_modules**/*"
],
"preLaunchTask": "Build",
// "preLaunchTask": "Build",
"skipFiles": [
"<node_internals>/**"
],
Expand Down
82 changes: 50 additions & 32 deletions build/mocha-esm-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ export async function resolve(specifier, context, nextResolve) {
};
}

// Intercept @deepnote/convert - needed because the real package performs file I/O
// that we need to control in tests
if (specifier === '@deepnote/convert') {
// Intercept @deepnote/runtime-core - needed because the real startServer/stopServer
// spawn/kill real Python processes. The mock records calls and returns a fake ServerInfo
// (faithful to the { url, jupyterPort, lspPort, process } contract) so the extension's
// keying/working-directory/lifecycle logic can be tested without a real server.
if (specifier === '@deepnote/runtime-core') {
return {
url: 'vscode-mock:///deepnote-convert',
url: 'vscode-mock:///deepnote-runtime-core',
shortCircuit: true
};
}
Expand Down Expand Up @@ -328,6 +330,7 @@ export async function load(url, context, nextLoad) {
export const NotebookRendererMessaging = createClassProxy('NotebookRendererMessaging');
export const NotebookRendererScript = createClassProxy('NotebookRendererScript');
export const NotebookVariableProvider = createClassProxy('NotebookVariableProvider');
export const TabInputNotebook = createClassProxy('TabInputNotebook');
export const ColorThemeKind = createClassProxy('ColorThemeKind');
export const UIKind = createClassProxy('UIKind');
export const ThemeIcon = createClassProxy('ThemeIcon');
Expand All @@ -352,39 +355,54 @@ export async function load(url, context, nextLoad) {
};
}

// Handle deepnote convert mock - needed because the real package performs file I/O
if (moduleName === 'deepnote-convert') {
// Handle @deepnote/runtime-core mock - needed because the real startServer/stopServer
// spawn/kill real Python processes. The mock keeps a shared call log so tests can
// assert how many servers were started/stopped and with which working directories.
if (moduleName === 'deepnote-runtime-core') {
return {
format: 'module',
source: `
export const convertIpynbFilesToDeepnoteFile = async () => {
// Mock implementation - does nothing in tests
const startServerCalls = [];
const stopServerCalls = [];
let nextServerId = 0;
let startServerImpl = null;

const makeFakeProcess = (id) => ({
pid: 40000 + id,
stdout: { on() {}, off() {} },
stderr: { on() {}, off() {} },
kill() {}
});

export const startServer = async (options) => {
startServerCalls.push(options);
if (startServerImpl) {
return startServerImpl(options);
}
const id = nextServerId++;
return {
url: 'http://127.0.0.1:' + (50000 + id),
jupyterPort: 50000 + id,
lspPort: 51000 + id,
process: makeFakeProcess(id)
};
};

export const convertDeepnoteToJupyterNotebooks = (deepnoteFile) => {
// Mock implementation that converts Deepnote notebooks to Jupyter format
const notebooks = deepnoteFile?.project?.notebooks || [];
return notebooks.map(nb => ({
filename: nb.name.replace(/[<>:"/\\\\|?*]/g, '_').replace(/\\s+/g, '-') + '.ipynb',
notebook: {
cells: (nb.blocks || []).map(block => ({
cell_type: block.type === 'markdown' ? 'markdown' : 'code',
source: block.content || '',
metadata: {
deepnote_cell_type: block.type,
cell_id: block.id
},
outputs: block.outputs || []
})),
metadata: {
deepnote_notebook_id: nb.id,
deepnote_notebook_name: nb.name,
deepnote_execution_mode: nb.executionMode
},
nbformat: 4,
nbformat_minor: 5
}
}));
export const stopServer = async (info) => {
stopServerCalls.push(info);
};

// Test-only helpers (prefixed with __ to signal they are not part of the real API).
export const __getStartServerCalls = () => startServerCalls;
export const __getStopServerCalls = () => stopServerCalls;
export const __setStartServerImpl = (impl) => {
startServerImpl = impl;
};
export const __resetRuntimeCoreMock = () => {
startServerCalls.length = 0;
stopServerCalls.length = 0;
nextServerId = 0;
startServerImpl = null;
};
`,
shortCircuit: true
Expand Down
4 changes: 4 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"altuser",
"anyio",
"artmann",
"basenames",
"blockgroup",
"boto",
"channeldef",
Expand All @@ -50,6 +51,7 @@
"findstr",
"getsitepackages",
"IMAGENAME",
"initmain",
"ipykernel",
"ipynb",
"jupyter",
Expand Down Expand Up @@ -78,6 +80,7 @@
"PYTHONHOME",
"pyyaml",
"Reselecting",
"Résumé",
"rootpass",
"scikit",
"scipy",
Expand All @@ -95,6 +98,7 @@
"trino",
"Trino",
"unconfigured",
"unparseable",
"Unconfigured",
"unuse",
"unittests",
Expand Down
Loading
Loading