feat(deepnote): one notebook per .deepnote file#429
Conversation
…d) + add project-id resolver
Chunk 1 of single-notebook migration (§4 partial, §5). No behaviour change.
- Manager caches originals in a nested Map<projectId, Map<notebookId, project>>
so sibling files sharing a project.id no longer clobber each other.
- New API: getOriginalProject(projectId, notebookId) exact/no-fallback,
getAnyProjectEntry(projectId), storeOriginalProject/updateOriginalProject
(3-arg), updateProjectIntegrations iterates all entries.
- Update IDeepnoteNotebookManager and IPlatformDeepnoteNotebookManager; repoint
all project-level read-only callers to getAnyProjectEntry.
- Add canonical readDeepnoteProjectFile and resolveProjectIdFor{File,Notebook}.
- Selection state and init-run tracking intentionally kept (removed in later chunks).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…rop selection machinery Chunk 2 of single-notebook migration (§1 + Cleanup). - deserializeNotebook renders the first non-init notebook (findDefaultNotebook), falling back to the only/init notebook; never composes init. - serializeNotebook resolves the target from document metadata alone (projectId + notebookId required) and looks it up with the exact getOriginalProject, throwing clear errors instead of falling back to a wrong sibling. - detectContentChanges collapses to a single-notebook comparison. - Remove the ?notebook=<id> selection machinery: findCurrentNotebookId, the manager's selection state + interface methods, the explorer's query-param opens and selectNotebookForProject calls, and the tree item's custom resourceUri. - Explorer no longer depends on IDeepnoteNotebookManager. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…k siblings Chunk 3 of single-notebook migration (§0, §2, §3). - Add allocateSiblingUri: the single filesystem-aware, collision-safe sibling filename allocator (bumps -2/-3 before .deepnote, honors an in-batch reserved set, bounded retries). - Add a notebook file factory (buildSingleNotebookFile / buildSiblingNotebookFileUri) for creating sibling single-notebook files (wired into the explorer in a later chunk). - Add DeepnoteMultiNotebookSplitter: on opening a multi-notebook .deepnote file, offer to split it into one new single-notebook file per notebook. The action flushes the editor if dirty, writes all children, migrates the environment selection, then closes the tab and deletes the original to trash. A child-write failure leaves the original intact (write-before-delete). - Wire the splitter into activation with an optional (desktop-only) environment mapper; add a refresh() passthrough on the explorer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Chunk 4 of single-notebook migration (§6). - Add DeepnoteProjectMetadataPropagator (desktop): given a project id and a project-level mutator, enumerate every sibling .deepnote file on disk (open or closed), apply the change, and write it back. Skips no-op writes, refreshes the manager cache for open siblings, and collects per-file failures instead of aborting. Fires an onFileWritten hook so the file watcher treats each write as a self-write (no reload/save storm). - Route integration updates and project rename through the propagator so closed siblings stay consistent; web falls back to the cache-only / single-file paths. - Expose getOriginalProject/updateOriginalProject on the platform manager interface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…us bar Chunk 5 of single-notebook migration (§7). - Tree is grouped: ProjectGroup (by project id) -> ProjectFile -> Notebook. A single-notebook file is a leaf labelled with its notebook; legacy multi-notebook files stay collapsible. The init notebook is excluded from counts everywhere. - Refresh is grouping-safe: refreshNotebook evicts every sibling cache entry for a project id and all refreshes fire a full-tree change (no per-item fires). - Commands are project-scoped vs notebook-scoped; new/duplicate/add-notebook create sibling files via the factory (never appended), delete removes the file for a single-notebook file, and notebook names are unique within a project group. - Add a status bar item showing the active Deepnote notebook with a "Copy Active Deepnote Notebook Details" command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Chunk 6 of single-notebook migration (§8). - Key the server starter maps, the config handle, and the kernel auto-selector by notebook.uri.toString() - the same identity the kernel and controller use - so a notebook's server is 1:1 with its kernel. Sibling notebooks of one project no longer share a server; the working directory and SQL env are taken from each notebook's own file. - Fix environment deletion: stop every server using the environment (including closed notebooks whose server is still running) before removing the mappings, driven from the notebook->environment mapper. Drop the dead environmentServers map that was never populated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…le reader Chunk 7a of single-notebook migration (§9). - Write snapshots with notebook-scoped filenames via @deepnote/convert (generateSnapshotFilename / parseSnapshotFilename), replacing the local slug and filename regex. - readSnapshot resolves snapshots path-free (it runs at deserialize, which has no URI): glob by project id, rank the notebook-scoped match first and keep legacy project-scoped snapshots as a fallback, and skip an empty-output "latest" (save race) or a corrupt file while walking candidates. Legacy snapshots are read, never migrated or deleted. - Defer the execution snapshot save until outputs settle (quiet window with a max wait) and cancel it on re-execute / close. - Use convert's computeSnapshotHash on the save path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Chunk 7b of single-notebook migration (§10). - The init runner now subscribes to kernel start and restart events and runs the init notebook found in its own sibling .deepnote file (matched by project id + initNotebookId via isValidSiblingInitCandidate), instead of looking it up in the main file's notebooks. - Track "init has run" per kernel in a WeakSet<IKernel>: a fresh kernel runs init once, and an in-place restart (which fires onDidRestartKernel) re-runs it so the kernel is re-initialized before the next user cell. A missing sibling is logged and skipped without permanently marking the project. - Remove the manager's persistent init-run tracking and the selector's init staging; the runner owns init triggering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #429 +/- ##
===========================
===========================
🚀 New features to boost your workflow:
|
- void the fire-and-forget onExecutionComplete call (no-floating-promises), matching the existing void performSnapshotSave pattern. - Use American "behavior" in a comment. - Add test-only technical words (basenames, initmain, Résumé, unparseable) to cspell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…of duplicating it The mocha ESM loader wholesale-mocked @deepnote/convert and reimplemented its pure helpers (resolveSnapshotNotebookId, splitByNotebooks, isValidSiblingInitCandidate, snapshot filename generate/parse, hashing, etc.). That duplicated upstream logic with no drift detection: if convert changed, the mock silently kept the old behavior and tests stayed green against a fiction. - Remove the @deepnote/convert interception from build/mocha-esm-loader.js so unit tests exercise the real package's pure functions (and now track its actual API). - Mock only the one genuinely side-effecting export, convertIpynbFilesToDeepnoteFile (real node:fs I/O), via esmock in the explorer import suites where it is used. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…r paths From the Codex review of the PR (F1/F3/F4/F5), all independently verified: - F1 (P1): snapshot save fetched the cached project with getAnyProjectEntry(projectId), which can return the wrong sibling when multiple single-notebook siblings of one project are open, silently skipping the snapshot write. Use the exact getOriginalProject(projectId, notebookId) lookup instead. - F3: collectNotebookNamesForProject globbed **/*.deepnote without skipping snapshot sidecars, so stale snapshot notebook names polluted the name-uniqueness set. Filter snapshot files (matching the tree provider and propagator). - F4: detectContentChanges compared notebooks[0]; for a legacy [init, main] file the edited notebook is not at index 0, so edits were missed and modifiedAt preserved. Match the notebook by id. - F5: the deferred-save timer fired performSnapshotSave as a floating promise; wrap the save body in try/catch/finally so a build/write failure is logged (not an unhandled rejection) and execution state is always cleared. Adds regression tests for F1 (exact lookup), F3 (snapshot exclusion), and F4 (match by id). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…el init runs Addresses round-2 code-review findings G2 and G3 (both verified P2). - G2: deepnoteFileChangeWatcher's snapshot block-id recovery used the project-only getAnyProjectEntry(projectId), which can return a different open sibling's cached project (siblings share project.id), leaving originalBlocks undefined and silently skipping recovered outputs. Use the exact getOriginalProject(projectId, notebookId) — the same fix already applied to snapshotService (F1), here in the watcher path that was missed. - G3: moving init execution to the event-driven runner dropped the notebook-close cancellation that the kernel auto-selector used to provide, so closing a notebook mid-init left the remaining init blocks executing against a closed notebook. Tie the init run to a CancellationTokenSource cancelled on notebook close and dispose it in a finally. Adds regression tests for both (each fails on the pre-fix code). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
…ort/delete Removes two pieces of functionality; also folds in the branch's in-progress updates this work was layered on top of (they could not be isolated, as the removals are interleaved with and built on top of that WIP). Removed - project-metadata propagator: - Delete DeepnoteProjectMetadataPropagator and its types, drop the DI binding, and unwire it everywhere (activation, file-change watcher self-write hook, integration webview, explorer rename). Project-level fields are no longer fanned out across sibling files: each notebook owns its own integrations, and project-name drift is accepted for now. Drop the now-dead updateOriginalProject manager method and two stale comments. Removed - project-level explorer commands: - Delete the exportProject and deleteProject commands (constants, command-arg type, registrations, package.json command defs + sidebar menus, nls titles, and their unit tests). Per-notebook export remains via the existing exportNotebook command (first non-init notebook of the file). Also includes the branch's pending updates the above was built on: dependency bumps (incl. @deepnote/convert 4.0), the getOriginalProject -> getProjectForNotebook manager rename and getAnyProjectEntry removal, and assorted snapshot/serializer/kernel adjustments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Brings in #432 (Cloud SQL integration support). Resolved the package.json and package-lock.json conflicts by keeping this branch's newer @deepnote/* versions (blocks 4.6.0, convert 4.0.0, runtime-core 0.4.0); @deepnote/database-integrations is 1.5.0 on both sides, and the Cloud SQL source from #432 merged cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
@deepnote/blocks@4.6.0+ renders `text-cell-bullet` blocks with `indent_level >= 1` using leading spaces (two per level) before the bullet marker. stripMarkdown's bullet regex only matches at column 0, so the leading indentation must be trimmed first for the plain-text cell value to round-trip correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
…es re-exports snapshotFiles.ts re-exported six snapshot-filename helpers from @deepnote/convert. Remove the re-export block and import the helpers directly from @deepnote/convert at each use site (snapshotService.ts and the snapshotFiles unit test). snapshotFiles.ts now keeps only its local helpers (SNAPSHOT_FILE_SUFFIX, isSnapshotFile, extractProjectIdFromSnapshotUri) plus the single internal use of parseSnapshotFilename. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Refactor the buildSnapshotPath method to accept an object as an argument, improving readability and maintainability. Update all relevant calls to this method throughout the snapshotService and its unit tests to match the new signature. This change enhances the clarity of parameter usage and reduces the risk of errors when passing arguments.
Trim the single-notebook test suites by removing duplicate and tautological tests and collapsing/merging several others, shrinking the PR's test additions by ~640 lines with no loss of real coverage. Cuts target only tests this branch added: - exact-(projectId, notebookId)-lookup restatements duplicated across the watcher, serializer, snapshot, and manager suites - wrapper tests already covered by the delegate's own tests (addNotebookToProject, sibling-file allocation, project-id resolution) - tautologies over trivial template/getter functions (serverUtils) - framework-registration smoke tests (status bar) Merges keep the one meaningful assertion and drop the duplicate scaffolding (e.g. legacy-delete no-op folded into the existing delete test; two init builders parametrized into one). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
When splitting a legacy multi-notebook .deepnote file into single-notebook
siblings, rename the original to `<name>.deepnote.legacy` instead of moving it
to the OS trash. The `.legacy` suffix takes it out of the extension's view (it
no longer matches `*.deepnote`) while keeping it on disk next to the split
results, so the user can restore it by removing the suffix.
Unlike `workspace.fs.delete({ useTrash: true })`, this is deterministic and does
not depend on an OS trash backend (which can be absent on headless Linux).
Collisions bump the name to `.legacy-2`, `.legacy-3`, … and the rename still
happens only after every child is durably written (write-before-retire).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test that drives the real VS Code UI through the on-open split of a legacy multi-notebook .deepnote file: it asserts the split prompt, the one-file-per-notebook result, the retained `.legacy` backup, that each sibling opens without re-prompting, and that content plus the project integration fan out into every split file. Add a `createScreenshotter(this)` helper that captures step screenshots into a per-spec directory derived from the running test file (`test/e2e/screenshots/<spec>/`), plus the `sales-analytics.deepnote` fixture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
…ites Add ExTester end-to-end suites covering: - opening a plain single-notebook file (opens directly, no split prompt, the status bar shows the notebook name); - splitting a multi-notebook file that declares an init notebook (the init notebook becomes its own single-notebook sibling; each main sibling still references it via initNotebookId); - the init-notebook runner: the sibling init notebook runs hidden in a main notebook's kernel so its definitions are available, and re-runs after a kernel restart. Add the quick-notes and etl-pipeline fixtures (including the pre-split extract/init siblings), and disable the kernel-restart confirmation in the E2E settings so the restart test can drive it non-interactively. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test asserting the Deepnote Explorer groups sibling
.deepnote files by project: three files sharing one project.id collapse into a
single "Marketing" group ("3 files") whose leaves are the three notebooks, while
a file from a different project appears as its own group. Reads the tree by
diffing visible leaves before/after expanding (avoids the page-object library's
flaky CustomTreeItem.getChildItems). Adds the three marketing fixtures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
When several E2E suites run in one ExTester session (as in CI, via the `*.e2e.test.js` glob), every workspace-folder open after the first failed with "Failed to open folder after 5 attempts" in the suite's `before all` hook — only the alphabetically-first suite passed. Root cause: the simple "Open Folder" dialog (files.simpleDialog.enable) navigates one directory level *toward* the typed path per OK click and only accepts the folder once the browser is AT it. The helper clicked OK once then re-opened the dialog each attempt, which reset navigation back to the default directory — for the 2nd+ open that default is the previous, now-deleted workspace, so the dialog fell back to "/" and never converged on the target. Fix: click OK repeatedly within a single dialog until the pre-open workbench element detaches (reload = folder accepted), instead of re-opening per attempt; and set `window.openFoldersInNewWindow: "off"` so "Open Folder" reuses the current window, keeping that reload detectable. Verified with four suites (16 tests) opening four folders in one session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the Deepnote status-bar item: it shows the active notebook's name (with the "Copy Active Deepnote Notebook Details" tooltip), hides when a non-notebook editor is focused, and — on click — copies the notebook details to the clipboard with a confirmation toast. The clipboard is verified by pasting into a scratch text file and reading it back. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the notebook-management commands that create and rename sibling .deepnote files from the Deepnote explorer: New Notebook, Add Notebook (project-group context menu), Duplicate Notebook, and Rename Notebook — each verified by the resulting notebook name inside the sibling files plus the confirmation toast. Delete Notebook is included as a pending test: its context-menu -> native confirmation-modal interaction is unreliable to drive under ExTester (documented inline), so it is left as a manual check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for the Deepnote integrations UI: opening "Manage Integrations" for a notebook whose project declares an integration lists it (the "Sales BigQuery" integration on the sales-analytics-revenue fixture), while a plain notebook (quick-notes) shows no such integration. Adds the sales-analytics-revenue fixture (a single-notebook split of the Sales Analytics project carrying the BigQuery integration + its SQL cell). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Add an ExTester end-to-end test for renaming a Deepnote project from the Explorer: none of the three "Marketing" siblings is opened, then the project group is renamed to "Growth" via its context menu; the new name is asserted to fan out to every sibling .deepnote file on disk (and the old name gone), with the Explorer group relabelled and a confirmation toast. Extract the shared Deepnote tree helpers (getDeepnoteExplorerSection, readDeepnoteTreeRows, findDeepnoteGroup/Leaf, selectDeepnoteContextMenu) into test/e2e/helpers/deepnoteTree.ts for reuse across the tree-driven suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
✅ Action performedFull review finished. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughDeepnote notebook identity is now notebook-scoped across caching, serialization, snapshots, kernels, server handles, integrations, and explorer actions. The explorer groups sibling Estimated code review effort: 5 (Critical) | ~150 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/notebooks/deepnote/deepnoteExplorerView.ts (1)
900-934: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCheck all import targets in one collision set.
The current checks miss collisions between selected
.deepnotefiles and Jupyter-derived.deepnoteoutputs, and between two selected.deepnotefiles with the same basename. Build oneseenTargetNamesset for all selected outputs before any writes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 900 - 934, The import flow in deepnoteExplorerView should validate all output names together instead of checking only existing workspace files per .deepnote input. In the import logic around checkJupyterImportTargetsAvailable and convertJupyterUrisToDeepnoteFiles, build a single seenTargetNames set for every selected output basename before any writes, and reject duplicates across selected .deepnote files and Jupyter-derived .deepnote targets as well as collisions with existing workspace files.src/kernels/deepnote/types.ts (1)
174-179: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStale JSDoc on
stopServer:@param environmentIdno longer exists.The method now takes
deepnoteFileUri, but the doc still documents@param environmentId The environment IDand the summary says "for a kernel environment" instead of a notebook. Compare withstartServer's doc a few lines up, which was correctly updated to describe notebook-URI keying.📝 Fix stale doc
/** - * Stops the deepnote-toolkit server for a kernel environment. - * `@param` environmentId The environment ID + * Stops the deepnote-toolkit server for a notebook. + * `@param` deepnoteFileUri The URI of the .deepnote file * `@param` token Cancellation token to cancel the operation */ stopServer(deepnoteFileUri: vscode.Uri, token?: vscode.CancellationToken): Promise<void>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/kernels/deepnote/types.ts` around lines 174 - 179, The JSDoc for stopServer is stale and still describes the old environmentId-based API. Update the comment above stopServer in the Deepnote types so the summary matches its notebook-URI-based behavior and the `@param` entry documents deepnoteFileUri instead of environmentId, keeping it consistent with the nearby startServer documentation.src/notebooks/deepnote/deepnoteFileChangeWatcher.ts (1)
568-607: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winDuplicate self-write marker logic.
markSelfWriteandmarkSnapshotSelfWriteare structurally identical, differing only in which Set/Map pair they touch. As per coding guidelines, "Extract duplicate logic into helper methods to prevent drift following DRY principle."♻️ Proposed consolidation
- private markSelfWrite(uri: Uri): void { - const key = this.selfWriteKey(uri); - this.selfWriteUris.add(key); - - // Safety net: clean stale entries after 30s - const existing = this.selfWriteTimers.get(key); - if (existing) { - clearTimeout(existing); - } - this.selfWriteTimers.set( - key, - setTimeout(() => { - this.selfWriteUris.delete(key); - this.selfWriteTimers.delete(key); - }, selfWriteExpirationMs) - ); - } + private markSelfWrite(uri: Uri): void { + this.markSelfWriteInternal(uri, this.selfWriteUris, this.selfWriteTimers); + } + + private markSelfWriteInternal( + uri: Uri, + uris: Set<string>, + timers: Map<string, ReturnType<typeof setTimeout>> + ): void { + const key = this.selfWriteKey(uri); + uris.add(key); + + const existing = timers.get(key); + if (existing) { + clearTimeout(existing); + } + timers.set( + key, + setTimeout(() => { + uris.delete(key); + timers.delete(key); + }, selfWriteExpirationMs) + ); + } private markSnapshotSelfWrite(uri: Uri): void { - const key = this.selfWriteKey(uri); - this.snapshotSelfWriteUris.add(key); - - // Safety net: clean stale entries after 30s - const existing = this.snapshotSelfWriteTimers.get(key); - if (existing) { - clearTimeout(existing); - } - this.snapshotSelfWriteTimers.set( - key, - setTimeout(() => { - this.snapshotSelfWriteUris.delete(key); - this.snapshotSelfWriteTimers.delete(key); - }, selfWriteExpirationMs) - ); + this.markSelfWriteInternal(uri, this.snapshotSelfWriteUris, this.snapshotSelfWriteTimers); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteFileChangeWatcher.ts` around lines 568 - 607, `markSelfWrite` and `markSnapshotSelfWrite` duplicate the same timer/cleanup flow and should be consolidated to avoid drift. Extract the shared self-write tracking behavior into a helper in `deepnoteFileChangeWatcher` that accepts the target Set and Map (or equivalent parameters), then have both methods delegate to it while preserving their distinct markers and `selfWriteKey` usage.Source: Coding guidelines
🧹 Nitpick comments (30)
.vscode/launch.json (1)
20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
preLaunchTaskdisabled only for the main "Extension" config.Debugging will now run against stale
dist/output unless devs remember to build manually first, while every other launch config still builds automatically. If intentional (e.g. faster iteration with a separate watch task), fine — otherwise this is likely to cause confusing "my fix isn't showing up" reports.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.vscode/launch.json at line 20, The main “Extension” launch config has `preLaunchTask` disabled, so debugging can use stale `dist/` output while the other configs still build automatically. Either re-enable `preLaunchTask` for the `Extension` entry in `launch.json`, or make the intent explicit by wiring it to the same build/watch workflow used by the other launch configurations so `dist/` stays up to date.build/mocha-esm-loader.js (1)
107-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTruncated comment.
// Intercept@deepnote/runtime-core- needed because the real startServer/stopServercuts off mid-sentence. The equivalent comment at Line 355-357 completes the thought ("spawn/kill real Python processes..."). Consider mirroring that text here for consistency.✏️ Suggested fix
- // Intercept `@deepnote/runtime-core` - needed because the real startServer/stopServer + // Intercept `@deepnote/runtime-core` - needed because the real startServer/stopServer + // spawn/kill real Python processes. if (specifier === '`@deepnote/runtime-core`') {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build/mocha-esm-loader.js` around lines 107 - 113, The comment in the `@deepnote/runtime-core` intercept block is truncated and should be made consistent with the later explanatory comment in the same loader. Update the inline comment near the specifier check in mocha-esm-loader.js so it mirrors the full wording used elsewhere about avoiding real startServer/stopServer behavior that would spawn/kill real Python processes, keeping the intent clear and consistent across the file.src/notebooks/deepnote/deepnoteSiblingFileAllocator.ts (1)
11-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor ordering nit vs. alphabetical convention.
As per coding guidelines, order exports alphabetically;
allocateSiblingUrishould precededeepnoteFileExists.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteSiblingFileAllocator.ts` around lines 11 - 61, The exported helpers in deepnoteSiblingFileAllocator are not in alphabetical order, so reorder the exports so allocateSiblingUri appears before deepnoteFileExists. Keep the existing implementations intact and only adjust the declaration order in this module, including the splitBasename helper remaining non-exported below them.Source: Coding guidelines
src/notebooks/deepnote/deepnoteNotebookFileFactory.ts (1)
15-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueFunction order doesn't follow alphabetical convention.
As per coding guidelines,
**/*.{ts,tsx}files should order methods/fields alphabetically. Current order isgetFileStem,buildSingleNotebookFile,buildSiblingNotebookFileUri; alphabetical would putbuildSiblingNotebookFileUri/buildSingleNotebookFilefirst.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteNotebookFileFactory.ts` around lines 15 - 61, The exported functions in deepnoteNotebookFileFactory are not ordered alphabetically. Reorder the declarations in this module so the build* functions come before getFileStem, keeping the existing behavior unchanged and preserving the current symbols buildSiblingNotebookFileUri, buildSingleNotebookFile, and getFileStem.Source: Coding guidelines
src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts (3)
75-213: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrivate methods not in alphabetical order.
Order is
handleNotebookOpened,splitFile,allocateLegacyUri,closeNotebookTab; alphabetical would beallocateLegacyUri,closeNotebookTab,handleNotebookOpened,splitFile.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts` around lines 75 - 213, The private methods in DeepnoteMultiNotebookSplitter are not ordered alphabetically, so rearrange them to match the expected order: allocateLegacyUri, closeNotebookTab, handleNotebookOpened, then splitFile. Keep the method bodies unchanged and move only the declarations in the class so the ordering is consistent for maintainability.Source: Coding guidelines
52-55: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAvoid
voidfor fire-and-forget async calls.Based on learnings, this repo prefers explicit handling (
await, return the promise, or.catch(...)) overvoid someAsyncFn(). Line 60's surroundingtry/catchalso can't catch async rejections from thevoid-wrapped call — it only guards synchronous throws, so it's misleading as written.♻️ Proposed fix
- workspace.onDidOpenNotebookDocument((notebook) => { - void this.handleNotebookOpened(notebook); - }) + workspace.onDidOpenNotebookDocument((notebook) => { + this.handleNotebookOpened(notebook).catch((error) => + this.logger.error('Unhandled error inspecting Deepnote notebook for split', error) + ); + })- for (const notebook of workspace.notebookDocuments) { - try { - void this.handleNotebookOpened(notebook); - } catch (error) { - this.logger.error('Failed to inspect open Deepnote notebook for multi-notebook split', error); - } - } + for (const notebook of workspace.notebookDocuments) { + this.handleNotebookOpened(notebook).catch((error) => + this.logger.error('Failed to inspect open Deepnote notebook for multi-notebook split', error) + ); + }Also applies to: 58-64
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts` around lines 52 - 55, The fire-and-forget call in onDidOpenNotebookDocument is using void around handleNotebookOpened, which this repo avoids and which bypasses the surrounding try/catch for async failures. Update the callback to explicitly handle the promise from this.handleNotebookOpened(notebook) by awaiting it in an async handler, returning the promise, or chaining a .catch(...) so rejections are handled consistently. Keep the fix centered on DeepnoteMultiNotebookSplitter and its handleNotebookOpened usage.Source: Learnings
187-198: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
allocateLegacyUriduplicates the bounded-attempt allocation loop shape used byallocateSiblingUri.Both loops walk numbered suffixes up to a max-attempts cap and throw a similar error; consider a small shared helper (e.g., a generic
findFreeName(candidates, exists)generator) to avoid drift between the two cap/error-message conventions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts` around lines 187 - 198, The bounded allocation loop in allocateLegacyUri duplicates the same numbered-suffix pattern already used by allocateSiblingUri, so refactor both to share a small helper for trying candidate names and checking exists(). Extract the common attempt/max-cap logic into a reusable helper in deepnoteMultiNotebookSplitter.ts, then have allocateLegacyUri and allocateSiblingUri both call it so the suffix-generation and error-message conventions stay aligned. Keep the existing behavior and unique symbols (allocateLegacyUri, allocateSiblingUri, MAX_LEGACY_ALLOCATION_ATTEMPTS) intact while removing the duplicated loop shape.Source: Coding guidelines
src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts (1)
879-888: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse one object comparison for the written notebook shape.
These assertions validate one object shape; prefer
assert.deepStrictEqual()on the relevant picked fields. As per coding guidelines, “Useassert.deepStrictEqual()for object comparisons instead of checking individual properties.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts` around lines 879 - 888, The updated notebook shape check in the Deepnote explorer test is asserting the same object field-by-field, so replace the individual property expectations with one deep object comparison. In the test around deserializeDeepnoteFile and updatedProjectData.project.notebooks[0], compare the picked notebook fields with assert.deepStrictEqual() against the expected object shape, keeping the project id and notebook contents validation but avoiding separate per-property assertions.Source: Coding guidelines
src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.ts (2)
20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
DEEPNOTE_NOTEBOOK_TYPEconstant.This re-declares a constant already exported from
src/kernels/deepnote/types.ts. Import the shared constant instead of duplicating the literal — prevents drift if the canonical value ever changes.♻️ Proposed fix
-const DEEPNOTE_NOTEBOOK_TYPE = 'deepnote'; +import { DEEPNOTE_NOTEBOOK_TYPE } from '../../kernels/deepnote/types';As per path instructions, "Extract duplicate logic into helper methods to prevent drift following DRY principle."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.ts` at line 20, Duplicate DEEPNOTE_NOTEBOOK_TYPE is being re-declared instead of reusing the shared source of truth. Update deepnoteNotebookInfoStatusBar to import the exported DEEPNOTE_NOTEBOOK_TYPE from src/kernels/deepnote/types.ts and remove the local literal so the status bar logic uses the canonical constant consistently.Source: Path instructions
72-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrivate methods aren't alphabetically ordered.
Current order (handleActiveEditorChanged → handleNotebookDocumentChanged → updateStatusBar → copyActiveNotebookDetails → formatNotebookDetails) isn't alphabetical.
As per coding guidelines, "Order method, fields and properties, first by accessibility and then by alphabetical order."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.ts` around lines 72 - 137, The private methods in the Deepnote notebook info status bar class are not ordered alphabetically after accessibility. Reorder the private members in this block so the methods in DeepnoteNotebookInfoStatusBar follow the guideline by alphabetical name, keeping related behavior unchanged; specifically adjust the order of handleActiveEditorChanged, handleNotebookDocumentChanged, updateStatusBar, copyActiveNotebookDetails, and formatNotebookDetails.Source: Coding guidelines
src/notebooks/deepnote/integrations/integrationManager.ts (1)
98-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated project/notebook ID extraction pattern.
The
const projectId = ...deepnoteProjectId; const notebookId = ...deepnoteNotebookId; if (!projectId || !notebookId) {...}pattern appears here twice (Lines 99-105, 130-135) and again infederatedAuthKernelRestartBridge.node.ts(Lines 60-64). Extracting a shared helper (e.g.getDeepnoteNotebookIdentifiers(notebook)) would prevent drift as more consumers adopt notebook-scoping.♻️ Suggested helper
export function getDeepnoteNotebookIdentifiers( notebook: NotebookDocument ): { projectId: string; notebookId: string } | undefined { const projectId = notebook.metadata?.deepnoteProjectId as string | undefined; const notebookId = notebook.metadata?.deepnoteNotebookId as string | undefined; return projectId && notebookId ? { projectId, notebookId } : undefined; }As per coding guidelines, "Extract duplicate logic into helper methods to prevent drift following DRY principle".
Also applies to: 131-132
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/integrations/integrationManager.ts` around lines 98 - 108, The Deepnote notebook project/notebook ID lookup is duplicated in IntegrationManager and should be centralized to avoid drift. Extract the repeated metadata access and missing-ID guard in integrationManager.ts into a shared helper such as getDeepnoteNotebookIdentifiers(notebook), then use that helper wherever projectId/notebookId are needed, including the other occurrence in IntegrationManager and the similar logic in federatedAuthKernelRestartBridge.node.ts. Keep the existing early-return behavior when identifiers are missing, but source it from the helper so all callers stay consistent.Source: Coding guidelines
src/notebooks/deepnote/sqlCellStatusBarProvider.ts (1)
233-234: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistent guard style for missing
notebookId.Line 233-234 silently falls back to
undefinedproject whennotebookIdis missing, while line 330-334 explicitly surfaces an error for the same missing-metadata case. Under the new single-notebook-per-file model this should rarely trigger, but aligning both paths (e.g., both silent-degrade or both explicit) would make the missing-metadata handling more predictable.Also applies to: 330-337
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/sqlCellStatusBarProvider.ts` around lines 233 - 234, The missing-`notebookId` handling in the status bar provider is inconsistent between the project lookup path and the later metadata check. Align the behavior in `sqlCellStatusBarProvider` by choosing one approach for both `getProjectForNotebook` usage and the explicit `deepnoteNotebookId` validation path: either consistently return a safe fallback without special logging, or consistently surface an error when metadata is absent. Keep the handling centered around the existing `notebookId`, `project`, and the later missing-metadata branch so the behavior is predictable across both code paths.src/notebooks/deepnote/integrations/integrationWebview.unit.test.ts (1)
438-481: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider a regression test for
projectNamecaching.Good coverage of the cache-only update path. No test exercises the
projectNameoverwrite behavior flagged inintegrationWebview.ts(callingshow()a second time withoutprojectNameclears the cached value). Worth a follow-up test once that behavior is confirmed/resolved.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/integrations/integrationWebview.unit.test.ts` around lines 438 - 481, Add a regression test around the `show()` flow in `IntegrationWebviewProvider` to cover `projectName` caching: verify that a second call to `show()` without a `projectName` does not clear an existing cached name, or update the implementation if the overwrite behavior in `integrationWebview.ts` is unintended. Reuse the existing `show(provider, ...)` helper and the `IntegrationWebviewProvider` setup so the test directly exercises the cache state transition.test/e2e/suite/environment.e2e.test.ts (1)
58-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate stale-element reload detection vs.
workspace.ts'sopenFolderViaDialog.
reloadWindowre-implements the same "pollpreviousWorkbench.getTagName()until it throws" pattern already intest/e2e/helpers/workspace.ts(lines 58-70), just with different timeout/retry wiring. The two have already diverged (one-shot wait here vs. retry loop there) — a good candidate to consolidate into a shared helper (e.g.,waitForWorkbenchStale(el, timeout)) inhelpers/before they drift further apart.As per coding guidelines: "Extract duplicate logic into helper methods to prevent drift following DRY principle."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/suite/environment.e2e.test.ts` around lines 58 - 83, The stale-element reload detection in reloadWindow duplicates the same polling logic already used by openFolderViaDialog in workspace.ts, so extract it into a shared helper under helpers/ to keep the behavior consistent. Create a reusable wait-for-stale-workbench utility that encapsulates the previousWorkbench.getTagName() retry pattern and timeout handling, then have reloadWindow call that helper instead of inlining its own wait/catch flow.Source: Coding guidelines
test/e2e/helpers/workspace.ts (1)
33-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDoc wording differs slightly from
clickDialogOkButton's JSDoc.This comment says clicking OK "navigates one level toward the typed path rather than accepting it" (hence the retry loop), while
quickInput.ts's updated JSDoc forclickDialogOkButtoncalls it "the deterministic accept" in contrast to Enter. Worth tightening the wording in one place so a future reader isn't confused about whether a single OK click accepts or only converges after several clicks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/helpers/workspace.ts` around lines 33 - 37, Tighten the wording between the workspace helper docs and clickDialogOkButton’s JSDoc so they describe the same OK-button behavior. Update the comment in workspace.ts around the open-absolute-folder retry loop to match the terminology used by clickDialogOkButton in quickInput.ts, clarifying whether OK is the deterministic accept action or a step toward acceptance, and keep the explanation consistent across both symbols.test/e2e/suite/notebookCommands.e2e.test.ts (1)
63-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded sleep durations.
sleep(1200),sleep(800),sleep(500),sleep(1500)are inline literals rather than named constants (unlikeTREE_LOAD_TIMEOUT/WORKBENCH_TIMEOUTused elsewhere in this file).As per coding guidelines,
**/*.{ts,tsx}: "Extract magic numbers (retry counts, delays, timeouts) as named constants near the top of the module."Also applies to: 97-97, 113-113, 183-183
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/suite/notebookCommands.e2e.test.ts` at line 63, The notebookCommands e2e test uses hardcoded sleep delays, which should be replaced with named constants near the top of the module. Update the test cases in notebookCommands.e2e.test.ts that use sleep(1200), sleep(800), sleep(500), and sleep(1500) to reference shared timeout/delay constants, following the existing TREE_LOAD_TIMEOUT and WORKBENCH_TIMEOUT pattern so the timing values are centralized and easier to maintain.Source: Coding guidelines
test/e2e/suite/explorerGrouping.e2e.test.ts (3)
98-129: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded sleep durations.
driver.sleep(2000)(Line 104) anddriver.sleep(1000)(Line 128) are inline magic numbers;TREE_LOAD_TIMEOUTis already extracted as a constant above, but these aren't.As per coding guidelines,
**/*.{ts,tsx}: "Extract magic numbers (retry counts, delays, timeouts) as named constants near the top of the module."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/suite/explorerGrouping.e2e.test.ts` around lines 98 - 129, The e2e test in explorerGrouping.e2e.test.ts still uses inline delay values in the Explorer setup and expand flow, which should be extracted as named constants like the existing TREE_LOAD_TIMEOUT. Replace the hardcoded wait durations used after opening the Deepnote view and after expanding the Marketing group with clearly named top-of-module constants, and reference them from the test body to keep timing values centralized and maintainable. Keep the logic in the same test helpers and flow around getExplorerSection, readRows, and the marketing.expand call unchanged apart from swapping in the constants.Source: Coding guidelines
89-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated fixture-copy loop.
Manually copying additional sibling fixtures via
fs.copyFileSyncduplicates the same pattern innotebookCommands.e2e.test.ts(Lines 154-156). Consider extendingcopyFixtureToTempDir(or adding acopyFixturesToTempDir(names[])) to cover multi-file copies once, infixtures.ts.copyFixtureToTempDir resolves fixtureName from test/e2e/fixtures, copies it into a temp directory, and returns a cleanup function. Extending it to accept multiple names would remove this repeated boilerplate.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/suite/explorerGrouping.e2e.test.ts` around lines 89 - 96, The test setup repeats manual sibling-fixture copying, duplicating the pattern used elsewhere and making the e2e tests harder to maintain. Update the shared fixture helper in fixtures.ts, specifically copyFixtureToTempDir, to support copying multiple fixture names at once (or add a copyFixturesToTempDir(names[]) helper) and use that from explorerGrouping.e2e.test.ts instead of the inline fs.copyFileSync loop, so the multi-file copy behavior lives in one place.
30-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the shared Deepnote tree helpers
test/e2e/suite/explorerGrouping.e2e.test.ts:30-84still duplicates the same tree-reading helpers already exported fromtest/e2e/helpers/deepnoteTree.ts. Import those instead of keeping local copies.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/suite/explorerGrouping.e2e.test.ts` around lines 30 - 84, The Explorer grouping e2e test still duplicates tree helper logic instead of reusing the shared Deepnote helpers. Replace the local helper implementations in explorerGrouping.e2e.test.ts, including getExplorerSection and readRows and the label/description accessors, by importing and using the existing exports from deepnoteTree.ts so the test relies on one shared source of truth.Source: Coding guidelines
test/e2e/helpers/deepnoteTree.ts (1)
20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInline sleep magic numbers bypass the shared constants module.
1200,800,500ms sleeps are hardcoded here whiletest/e2e/helpers/constants.tsexists precisely to centralize such timeouts/delays.As per coding guidelines, "Extract magic numbers (retry counts, delays, timeouts) as named constants near the top of the module."
♻️ Proposed fix
+// in constants.ts +export const EXPLORER_VIEW_OPEN_DELAY = 1_200; +export const TREE_EXPAND_DELAY = 800; +export const CONTEXT_MENU_OPEN_DELAY = 500;- await VSBrowser.instance.driver.sleep(1200); + await VSBrowser.instance.driver.sleep(EXPLORER_VIEW_OPEN_DELAY); ... - await VSBrowser.instance.driver.sleep(800); + await VSBrowser.instance.driver.sleep(TREE_EXPAND_DELAY); ... - await driver.sleep(500); + await driver.sleep(CONTEXT_MENU_OPEN_DELAY);Also applies to: 75-75, 91-91
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/helpers/deepnoteTree.ts` at line 20, Hardcoded sleep delays are bypassing the shared timeout constants and should be centralized. Update the sleep calls in deepnoteTree helper methods to use named delay constants from test/e2e/helpers/constants.ts instead of inline values, and add any missing delay constants near the top of the module so the waits in the relevant helper functions stay consistent and easy to tune.Source: Coding guidelines
src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts (1)
255-287: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDead code:
onControllerSelectionChangedbody is entirely commented out.The reselection safety-net logic (notebookKey lookup, deselection check,
updateNotebookAffinity) is now fully commented rather than removed. If deliberately disabled, delete it instead of leaving it commented; if it was meant to stay active as a fallback, it's now a no-op.🧹 Suggested cleanup
- // const notebookKey = event.notebook.uri.toString(); - - // // If the Deepnote controller for this notebook was deselected, try to reselect it - // // Since controllers are now protected from disposal, this should rarely happen - // if (!event.selected) { - // const ourController = this.notebookControllers.get(notebookKey); - // if (ourController && ourController.id === event.controller.id) { - // logger.warn( - // `Deepnote controller was unexpectedly deselected for ${getDisplayPath( - // event.notebook.uri - // )}. Reselecting...` - // ); - // // Reselect the controller - // ourController.controller.updateNotebookAffinity(event.notebook, NotebookControllerAffinity.Preferred); - // } - // } + // Controllers are protected from disposal, so no reselection safety-net is needed here anymore.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts` around lines 255 - 287, The onControllerSelectionChanged method in deepnoteKernelAutoSelector.node.ts contains dead, fully commented-out reselection logic, so the handler is effectively a no-op beyond logging and notebookType gating. Remove the commented notebookKey / deselection / updateNotebookAffinity block from onControllerSelectionChanged if it is intentionally disabled, or restore it as live code if the fallback is still required; keep the behavior centered around the existing event, notebookControllers map, and NotebookControllerAffinity usage.src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts (2)
31-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated magic constant risks drift.
INIT_COMPLETE_DISPLAY_DELAY_MShere is a hand-copied duplicate of the production constant indeepnoteInitNotebookRunner.node.ts. If the production value changes, this test constant won't update and timing assumptions silently go stale.♻️ Suggested fix: export and import the constant
-const INIT_COMPLETE_DISPLAY_DELAY_MS = 1000; +// in deepnoteInitNotebookRunner.node.ts: export const INIT_COMPLETE_DISPLAY_DELAY_MS = 1000; +import { INIT_COMPLETE_DISPLAY_DELAY_MS } from './deepnoteInitNotebookRunner.node';As per coding guidelines, "Extract duplicate logic into helper methods to prevent drift following DRY principle."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts` around lines 31 - 36, The test is duplicating the production timing constant, which can drift from the real value. Export INIT_COMPLETE_DISPLAY_DELAY_MS from deepnoteInitNotebookRunner.node.ts and import it into deepnoteInitNotebookRunner.node.unit.test.ts, then keep RUN_FULLY_SETTLED_MS derived from that imported symbol so the test always tracks the runner’s actual delay.Source: Coding guidelines
125-402: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo test covers the same-kernel overlapping-run race.
Given the concurrency gap flagged in
deepnoteInitNotebookRunner.node.ts(doubleonDidStartKernelfire before the WeakSet is marked, oronDidRestartKernelfiring while a start-triggered run is still executing blocks), consider adding a regression test once that fix lands, to lock in the expected single-run/cancel-and-restart behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts` around lines 125 - 402, Add a regression test for the same-kernel overlapping-run race in DeepnoteInitNotebookRunner: cover the case where onDidStartKernel fires twice before the WeakSet marker is set, and the case where onDidRestartKernel fires while a start-triggered init is still executing. Use the existing DeepnoteInitNotebookRunner setup, makeKernel helper, executeHiddenSpy, and the onDidStartKernel/onDidRestartKernel emitters to assert only one init run happens in the overlap case and that restart behavior remains correct once the in-flight run is cancelled or completes.src/platform/deepnote/deepnoteServerUtils.node.unit.test.ts (1)
16-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCoverage gap vs. stated intent.
Suite docstring claims to pin the "exact format" and "byte-stability" of the handle, but only sibling-uniqueness is tested — no test asserts a fixed expected string or that identical inputs are idempotent.
✅ Suggested additional assertions
test('two different notebook URIs produce DIFFERENT handles (catches sibling collision)', () => { const uriA = Uri.file('/workspace/project/notebook-a.deepnote'); const uriB = Uri.file('/workspace/project/notebook-b.deepnote'); assert.notStrictEqual( createDeepnoteServerConfigHandle('env-1', uriA), createDeepnoteServerConfigHandle('env-1', uriB), 'sibling notebooks sharing one environment must still get distinct server handles' ); }); + + test('same inputs produce the SAME handle (idempotent/byte-stable)', () => { + const uri = Uri.file('/workspace/project/notebook-a.deepnote'); + assert.strictEqual( + createDeepnoteServerConfigHandle('env-1', uri), + createDeepnoteServerConfigHandle('env-1', uri) + ); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/platform/deepnote/deepnoteServerUtils.node.unit.test.ts` around lines 16 - 27, The current DeepnoteServerUtils test only checks that different notebook URIs produce different handles, but it does not verify the exact handle format or byte-stability promised by the suite description. Update the createDeepnoteServerConfigHandle coverage in deepnoteServerUtils.node.unit.test.ts to add assertions for a fixed expected handle string for a known env/URI pair and for idempotence by calling createDeepnoteServerConfigHandle with identical inputs multiple times and asserting the results are equal.src/notebooks/deepnote/deepnoteNotebookManager.ts (1)
12-22: 🩺 Stability & Availability | 🔵 TrivialNo eviction path for closed notebooks.
originalProjectsonly grows; nothing removes a(projectId, notebookId)entry when its notebook is closed/deleted. Worth confirming a cleanup hook exists elsewhere (e.g. on notebook close) to bound memory in long sessions with many opened/closed.deepnotesiblings.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteNotebookManager.ts` around lines 12 - 22, The notebook project cache in DeepnoteNotebookManager only ever adds entries to originalProjects and has no cleanup for closed or deleted notebooks. Add a removal path for the specific (projectId, notebookId) entry, ideally in the notebook close/delete lifecycle hook that owns notebook teardown, and make sure getProjectForNotebook still only reads the exact pair while the new cleanup keeps the cache bounded.src/platform/deepnote/deepnoteProjectFileReader.unit.test.ts (1)
65-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
deepStrictEqualfor the round-trip check.Six separate
assert.strictEqualcalls verify one round-tripped object; a singleassert.deepStrictEqual(result.project, original.project)(plus the version check) would be more concise and give a full diff on failure.As per path instructions,
**/*.unit.test.ts: "Useassert.deepStrictEqual()for object comparisons instead of checking individual properties."♻️ Proposed refactor
- assert.strictEqual(result.project.id, original.project.id); - assert.strictEqual(result.project.name, original.project.name); - assert.strictEqual(result.project.notebooks.length, 1); - assert.strictEqual(result.project.notebooks[0].id, 'notebook-1'); - assert.strictEqual(result.project.notebooks[0].name, 'Notebook One'); - assert.strictEqual(result.version, '1.0.0'); + assert.deepStrictEqual(result.project, original.project); + assert.strictEqual(result.version, original.version);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/platform/deepnote/deepnoteProjectFileReader.unit.test.ts` around lines 65 - 70, The round-trip assertion in the deepnoteProjectFileReader unit test is comparing the project object property-by-property instead of using a full object comparison. Update the test in deepnoteProjectFileReader.unit.test.ts to use assert.deepStrictEqual for result.project versus original.project, and keep the separate version assertion; this aligns with the test helper guidance and gives clearer failure diffs.Source: Path instructions
src/notebooks/deepnote/snapshots/snapshotService.ts (2)
478-497: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winWrap per-folder scanning in try/catch so one bad workspace folder doesn't abort the whole scan.
workspace.findFilesisn't guarded per folder here, unlike the per-candidate read loop below it. A single throwing folder aborts snapshot discovery for every other folder in a multi-root workspace.As per path instructions,
**/*.{ts,tsx}: "Use per-iteration error handling in loops - wrap each iteration in try/catch so one failure doesn't stop the rest."🔧 Proposed fix
for (const folder of workspaceFolders) { const pattern = new RelativePattern(folder, glob); - const files = await workspace.findFiles(pattern, null, maxSnapshotFilesPerFolder); - - for (const uri of files) { + let files: Uri[] = []; + try { + files = await workspace.findFiles(pattern, null, maxSnapshotFilesPerFolder); + } catch (error) { + logger.warn(`[Snapshot] Failed to scan folder ${folder.uri.toString()} for snapshots`, error); + continue; + } + + for (const uri of files) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/snapshots/snapshotService.ts` around lines 478 - 497, The per-folder snapshot scan in snapshotService’s workspace folder loop should be hardened so one failing folder does not stop discovery for the rest. Wrap the `workspace.findFiles` call inside the `for (const folder of workspaceFolders)` loop in a try/catch, keep the existing candidate filtering logic in `parseSnapshotFilename` and `candidates.push` unchanged, and continue to the next folder when an error occurs.Source: Path instructions
809-859: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNew private methods break the class's alphabetical ordering.
armSnapshotSave/cancelPendingSnapshotSave/handleNotebookDocumentChangeare inserted afterwaitForPendingCellStateChanges, andcompareSnapshotCandidates/extractOutputsFromFileafterperformSnapshotSave— both break alphabetical order among private methods.As per path instructions,
**/*.{ts,tsx}: "Order method, fields and properties, first by accessibility and then by alphabetical order."Also applies to: 1006-1046
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/snapshots/snapshotService.ts` around lines 809 - 859, The new private methods in SnapshotService are out of alphabetical order, so move armSnapshotSave, cancelPendingSnapshotSave, and handleNotebookDocumentChange into the correct position among the other private methods, and likewise place compareSnapshotCandidates and extractOutputsFromFile in alphabetical order near performSnapshotSave. Keep the existing accessibility grouping intact and reorder only the method declarations so the class follows the prescribed method ordering rule.Source: Path instructions
src/notebooks/deepnote/deepnoteSerializer.unit.test.ts (1)
280-284: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer
deepStrictEqualover chainedstrictEqualfor object shape checks.Same pattern as elsewhere in this PR — consolidate into one
deepStrictEqualwhere practical for clearer failure diffs.As per path instructions,
**/*.unit.test.ts: "Useassert.deepStrictEqual()for object comparisons instead of checking individual properties."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteSerializer.unit.test.ts` around lines 280 - 284, The notebook serialization test is checking the parsed notebook shape with multiple individual asserts instead of a single object comparison. In deepnoteSerializer.unit.test.ts, replace the chained property checks around parsed.project.notebooks with one assert.deepStrictEqual() against the expected notebook object shape, using the existing nbA/nbB and block-b symbols to keep the intent clear and the failure diff more useful.Source: Path instructions
src/notebooks/deepnote/snapshots/snapshotFiles.unit.test.ts (1)
124-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
assert.deepStrictEqualinstead of individual property checks.Lines 132-135 check
projectId,timestamp, andnotebookIdone at a time. As per coding guidelines, "Useassert.deepStrictEqual()for object comparisons instead of checking individual properties."♻️ Proposed fix
const parsed = parseSnapshotFilename(filename); - assert.isDefined(parsed); - assert.strictEqual(parsed!.projectId, projectId); - assert.strictEqual(parsed!.timestamp, 'latest'); - assert.isUndefined(parsed!.notebookId); + assert.deepStrictEqual(parsed, { slug: 'my-project', projectId, timestamp: 'latest' });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/snapshots/snapshotFiles.unit.test.ts` around lines 124 - 136, The legacy snapshot parsing test is asserting object fields one by one instead of comparing the parsed result as a whole. Update the test in the snapshot filename parsing suite to use assert.deepStrictEqual on the parsed object from parseSnapshotFilename, comparing it against the expected projectId, timestamp, and notebookId: undefined shape, while still keeping the existing undefined/null safety check if needed.Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e5c5c2ba-e1ea-419a-b234-e01823634219
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (114)
.gitignore.vscode/launch.jsonbuild/mocha-esm-loader.jscspell.jsonpackage.jsonpackage.nls.jsonsrc/commands.tssrc/kernels/deepnote/deepnoteLspClientManager.node.tssrc/kernels/deepnote/deepnoteLspClientManager.node.vscode.test.tssrc/kernels/deepnote/deepnoteServerStarter.node.tssrc/kernels/deepnote/deepnoteServerStarter.unit.test.tssrc/kernels/deepnote/environments/deepnoteEnvironmentManager.node.tssrc/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.tssrc/kernels/deepnote/environments/deepnoteEnvironmentsView.node.tssrc/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.tssrc/kernels/deepnote/environments/deepnoteExtensionSidecarWriter.node.tssrc/kernels/deepnote/types.tssrc/notebooks/deepnote/converters/textBlockConverter.tssrc/notebooks/deepnote/converters/textBlockConverter.unit.test.tssrc/notebooks/deepnote/deepnoteActivationService.tssrc/notebooks/deepnote/deepnoteExplorerView.tssrc/notebooks/deepnote/deepnoteExplorerView.unit.test.tssrc/notebooks/deepnote/deepnoteFileChangeWatcher.tssrc/notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.tssrc/notebooks/deepnote/deepnoteInitNotebookRunner.node.tssrc/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.tssrc/notebooks/deepnote/deepnoteKernelAutoSelector.node.tssrc/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.tssrc/notebooks/deepnote/deepnoteKernelStatusIndicator.node.tssrc/notebooks/deepnote/deepnoteKernelStatusIndicator.tssrc/notebooks/deepnote/deepnoteMultiNotebookSplitter.tssrc/notebooks/deepnote/deepnoteMultiNotebookSplitter.unit.test.tssrc/notebooks/deepnote/deepnoteNotebookFileFactory.tssrc/notebooks/deepnote/deepnoteNotebookFileFactory.unit.test.tssrc/notebooks/deepnote/deepnoteNotebookInfoStatusBar.tssrc/notebooks/deepnote/deepnoteNotebookInfoStatusBar.unit.test.tssrc/notebooks/deepnote/deepnoteNotebookManager.tssrc/notebooks/deepnote/deepnoteNotebookManager.unit.test.tssrc/notebooks/deepnote/deepnoteProjectUtils.tssrc/notebooks/deepnote/deepnoteSerializer.tssrc/notebooks/deepnote/deepnoteSerializer.unit.test.tssrc/notebooks/deepnote/deepnoteSiblingFileAllocator.tssrc/notebooks/deepnote/deepnoteSiblingFileAllocator.unit.test.tssrc/notebooks/deepnote/deepnoteTreeDataProvider.tssrc/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.tssrc/notebooks/deepnote/deepnoteTreeItem.tssrc/notebooks/deepnote/deepnoteTreeItem.unit.test.tssrc/notebooks/deepnote/integrations/federatedAuth/federatedAuthKernelRestartBridge.node.tssrc/notebooks/deepnote/integrations/federatedAuth/federatedAuthKernelRestartBridge.node.unit.test.tssrc/notebooks/deepnote/integrations/integrationDetector.tssrc/notebooks/deepnote/integrations/integrationManager.tssrc/notebooks/deepnote/integrations/integrationWebview.tssrc/notebooks/deepnote/integrations/integrationWebview.unit.test.tssrc/notebooks/deepnote/integrations/types.tssrc/notebooks/deepnote/snapshots/snapshotFiles.tssrc/notebooks/deepnote/snapshots/snapshotFiles.unit.test.tssrc/notebooks/deepnote/snapshots/snapshotService.tssrc/notebooks/deepnote/snapshots/snapshotService.unit.test.tssrc/notebooks/deepnote/sqlCellStatusBarProvider.tssrc/notebooks/deepnote/sqlCellStatusBarProvider.unit.test.tssrc/notebooks/serviceRegistry.node.tssrc/notebooks/serviceRegistry.web.tssrc/notebooks/types.tssrc/platform/common/constants.tssrc/platform/deepnote/deepnoteProjectFileReader.tssrc/platform/deepnote/deepnoteProjectFileReader.unit.test.tssrc/platform/deepnote/deepnoteProjectIdResolver.tssrc/platform/deepnote/deepnoteProjectIdResolver.unit.test.tssrc/platform/deepnote/deepnoteServerUtils.node.tssrc/platform/deepnote/deepnoteServerUtils.node.unit.test.tssrc/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.tssrc/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.tssrc/platform/notebooks/deepnote/types.tssrc/test/unittests.tssrc/webviews/webview-side/integrations/serviceAccountValidation.tstest/e2e/fixtures/bootstrap-only.deepnotetest/e2e/fixtures/etl-pipeline-extract.deepnotetest/e2e/fixtures/etl-pipeline-init.deepnotetest/e2e/fixtures/etl-pipeline.deepnotetest/e2e/fixtures/legacy-snapshot-demo.deepnotetest/e2e/fixtures/marketing-campaigns.deepnotetest/e2e/fixtures/marketing-metrics.deepnotetest/e2e/fixtures/marketing-overview.deepnotetest/e2e/fixtures/quick-notes.deepnotetest/e2e/fixtures/sales-analytics-revenue.deepnotetest/e2e/fixtures/sales-analytics.deepnotetest/e2e/fixtures/snapshots/legacy-snapshot-demo_ffffffff-ffff-4fff-8fff-ffffffffffff_latest.snapshot.deepnotetest/e2e/helpers/constants.tstest/e2e/helpers/deepnoteEnvironment.tstest/e2e/helpers/deepnoteTree.tstest/e2e/helpers/fixtures.tstest/e2e/helpers/index.tstest/e2e/helpers/modals.tstest/e2e/helpers/notebook.tstest/e2e/helpers/notifications.tstest/e2e/helpers/quickInput.tstest/e2e/helpers/screenshots.tstest/e2e/helpers/workspace.tstest/e2e/settings.jsontest/e2e/suite/environment.e2e.test.tstest/e2e/suite/explorerGrouping.e2e.test.tstest/e2e/suite/fileWatcher.e2e.test.tstest/e2e/suite/helloWorld.e2e.test.tstest/e2e/suite/initNotebookRunner.e2e.test.tstest/e2e/suite/initOnlyFile.e2e.test.tstest/e2e/suite/integrations.e2e.test.tstest/e2e/suite/notebookCommands.e2e.test.tstest/e2e/suite/openSingleNotebook.e2e.test.tstest/e2e/suite/projectRename.e2e.test.tstest/e2e/suite/snapshots.e2e.test.tstest/e2e/suite/splitInitNotebook.e2e.test.tstest/e2e/suite/splitMultiNotebook.e2e.test.tstest/e2e/suite/splitSafety.e2e.test.tstest/e2e/suite/statusBar.e2e.test.ts
💤 Files with no reviewable changes (2)
- src/commands.ts
- src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
…hem correctly - Add isSingleNotebookFile so an init-only .deepnote (whose only notebook is the init notebook) renders as a single-notebook leaf instead of a non-openable "0 notebooks" node (CodeRabbit #7). - findTreeItem returns the real leaf ProjectFile node for single-notebook files instead of a synthetic Notebook child that reveal() cannot target (#6). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
…latform consistency Unify cachedProjects/fileItemCache keys to uri.toString() (matching the watcher and kernel-selector convention) so file-change invalidation no longer misses on Windows, where uri.path differs from the native path. context.filePath stays native for Uri.file() via a lossless round-trip. (CodeRabbit #4) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
- renameProject warns with the count of files that could not be updated instead of a blanket success toast when some sibling writes fail (CodeRabbit #2). - Jupyter import converts each notebook independently (per-iteration try/catch) and reports failures, so one malformed file no longer aborts the batch (#11). - Pass native filesystem paths to the converter via getFilePath() instead of uri.path, fixing imports on Windows (#5). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
If a mid-loop write throws, best-effort delete the siblings already written this run before rethrowing, so a retry's name allocation isn't bumped to a duplicate by leftover files. (CodeRabbit #3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
- Guard init runs with a per-kernel in-flight CancellationTokenSource so overlapping start events don't run init twice and a restart cancels an in-flight run (CodeRabbit #9). - Replace void fire-and-forget snapshot calls with explicit .catch logging (#10). - Unregister the old server handle from the provider on environment switch so stale entries don't leak (#12). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
showIntegrationsUI bails when either the project or notebook ID is missing, so the message now reads "Cannot determine project or notebook ID". (CodeRabbit #8) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
- Extract the bare restart-settle delay in initNotebookRunner and the six openIntegrationsFor delays into named constants (CodeRabbit #13, #14). - Move the duplicated readStatusBarText, showView, and notebookCount helpers into test/e2e/helpers (statusBar/views/yaml) and import them in the specs (#15, #16). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
E2E caught that initOnlyFile.e2e.test.ts still asserted the pre-#7 "0 notebooks" Explorer node. Update it to assert the openable single-notebook leaf that #7 now renders (label = notebook name, "N cells"). Re-ran the file: 4 passing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
…ucceeds Capture the old handle up front and unregister it after ensureKernelSelectedWithConfiguration succeeds, and only if setup registered a different handle. A failed or cancelled environment switch no longer strands the still-selected controller on a dead handle (DeepnoteServerNotFoundError until reload). Adds rebuildController regression tests for the failed-switch and A->B paths, and corrects a stale placeholder-test comment. Fable review finding F1 (regression from the earlier server-handle unregister fix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Init-only files render as openable notebook leaves, so their Rename/Duplicate/ Export/Delete menus now appear. resolveTargetNotebook falls back to resolveLeafNotebook (was getNonInitNotebooks[0], empty for init-only), and the private single-notebook-file test delegates to the shared isSingleNotebookFile so delete removes the file instead of emptying it. Adds an E2E test that deletes an init-only leaf and asserts the file is removed with no "Notebook not found". Fable review finding F2 (gap from the init-only leaf rendering change). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
TreeView.reveal requires the provider to implement getParent; without it reveal always rejected and the command fell back to an info toast. Add getParent (Notebook -> ProjectFile, ProjectFile -> ProjectGroup) keyed to the existing uri.toString()/projectId caches. Adds an E2E test that reveals the active notebook and asserts the leaf is selected with no fallback toast. Fable review finding F3 (the earlier findTreeItem leaf-match was inert without this). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
… failure convertJupyterUrisToDeepnoteFiles returns the failure count; both import callers subtract it and suppress the success toast entirely when every conversion fails (=== 1 guard). No more "N imported successfully" alongside a failure warning. Adds unit tests for the partial-failure count and the all-failed case. Fable review finding F4 (consumer gap from the per-iteration import error handling). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/notebooks/deepnote/deepnoteExplorerView.ts (2)
668-691: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winDuplicate fallback-message template.
The identical "Active notebook: … in project …"
showInformationMessagecall is repeated verbatim in the not-found branch and the catch block.♻️ Proposed fix
+ const showFallbackMessage = () => + window.showInformationMessage( + `Active notebook: ${notebookMetadata?.deepnoteNotebookName || 'Untitled'} in project ${ + notebookMetadata?.deepnoteProjectName || 'Untitled' + }` + ); + try { const treeItem = await this.treeDataProvider.findTreeItem(projectId, notebookId); if (treeItem) { await this.treeView.reveal(treeItem, { select: true, focus: true, expand: true }); } else { - // Fall back to showing information if node not found - await window.showInformationMessage( - `Active notebook: ${notebookMetadata?.deepnoteNotebookName || 'Untitled'} in project ${ - notebookMetadata?.deepnoteProjectName || 'Untitled' - }` - ); + await showFallbackMessage(); } } catch (error) { - // Fall back to showing information if reveal fails this.logger.error('Failed to reveal notebook in explorer', error); - await window.showInformationMessage( - `Active notebook: ${notebookMetadata?.deepnoteNotebookName || 'Untitled'} in project ${ - notebookMetadata?.deepnoteProjectName || 'Untitled' - }` - ); + await showFallbackMessage(); }As per coding guidelines: "Extract duplicate logic into helper methods to prevent drift following DRY principle."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 668 - 691, The Active notebook fallback message is duplicated in both the not-found branch and the catch block, so extract that repeated `window.showInformationMessage` logic into a small helper in `DeepnoteExplorerView` and call it from both places. Keep the existing message text and metadata formatting the same, but centralize it so the `treeDataProvider.findTreeItem` and `treeView.reveal` flow stays DRY and avoids drift.Source: Coding guidelines
955-981: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDeepnote-file import loop lacks per-iteration error handling.
convertJupyterUrisToDeepnoteFiles(Line 854) already isolates per-item failures so one bad conversion doesn't stop the rest, but the sibling "Import deepnote files" loop at Line 956 has no try/catch: a single failedreadFile/writeFilethrows, aborting remaining.deepnotecopies and skipping the Jupyter conversion step entirely, with only a generic "Failed to import notebook" error surfacing — silently dropping any progress already made.🐛 Proposed fix
- // Import deepnote files - for (const deepnoteUri of deepnoteUris) { - const fileName = deepnoteUri.path.split('/').pop() || 'imported.deepnote'; - const targetUri = Uri.joinPath(workspaceFolder.uri, fileName); - - const content = await workspace.fs.readFile(deepnoteUri); - - await workspace.fs.writeFile(targetUri, content); - } + // Import deepnote files (per-iteration error handling so one failure doesn't stop the rest) + const failedDeepnoteNames: string[] = []; + + for (const deepnoteUri of deepnoteUris) { + const fileName = deepnoteUri.path.split('/').pop() || 'imported.deepnote'; + const targetUri = Uri.joinPath(workspaceFolder.uri, fileName); + + try { + const content = await workspace.fs.readFile(deepnoteUri); + await workspace.fs.writeFile(targetUri, content); + } catch (error) { + failedDeepnoteNames.push(fileName); + this.logger.error(`Failed to import Deepnote file ${fileName}`, error); + } + } - const failedCount = await this.convertJupyterUrisToDeepnoteFiles(jupyterUris, workspaceFolder.uri); + const failedJupyterCount = await this.convertJupyterUrisToDeepnoteFiles(jupyterUris, workspaceFolder.uri); + const failedCount = failedJupyterCount + failedDeepnoteNames.length;As per coding guidelines: "Use per-iteration error handling in loops - wrap each iteration in try/catch so one failure doesn't stop the rest."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 955 - 981, The “Import deepnote files” loop in deepnoteExplorerView should use per-iteration error handling so one failed read/write does not abort the rest of the imports or skip the subsequent Jupyter conversion step. Wrap each iteration around the workspace.fs.readFile/workspace.fs.writeFile flow in a try/catch, mirror the isolation already used by convertJupyterUrisToDeepnoteFiles, and continue processing remaining deepnoteUris while preserving any successful copies and reporting failures appropriately.Source: Coding guidelines
🧹 Nitpick comments (1)
src/notebooks/deepnote/deepnoteExplorerView.ts (1)
444-484: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSequential per-file reads in a workspace-wide scan.
collectNotebookNamesForProjectruns on every rename/duplicate/create/add-notebook action and reads every sibling.deepnotefile one at a time withawaitinside the loop. Parallelizing withPromise.allwould cut latency in workspaces with many project files.⚡ Proposed fix
- for (const fileUri of files) { - // Skip snapshot sidecars: they are full project clones whose stale notebook names - // would otherwise pollute the uniqueness set. - if (isSnapshotFile(fileUri)) { - continue; - } - - try { - const projectData = await readDeepnoteProjectFile(fileUri); - - if (projectData?.project?.id !== projectId) { - continue; - } - - for (const notebook of getNonInitNotebooks(projectData)) { - if (notebook.name && notebook.name !== excludeName) { - names.add(notebook.name); - } - } - } catch (error) { - this.logger.error(`Failed to read ${fileUri.path} for name collection`, error); - } - } + await Promise.all( + files.map(async (fileUri) => { + // Skip snapshot sidecars: they are full project clones whose stale notebook names + // would otherwise pollute the uniqueness set. + if (isSnapshotFile(fileUri)) { + return; + } + + try { + const projectData = await readDeepnoteProjectFile(fileUri); + + if (projectData?.project?.id !== projectId) { + return; + } + + for (const notebook of getNonInitNotebooks(projectData)) { + if (notebook.name && notebook.name !== excludeName) { + names.add(notebook.name); + } + } + } catch (error) { + this.logger.error(`Failed to read ${fileUri.path} for name collection`, error); + } + }) + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 444 - 484, `collectNotebookNamesForProject` is doing workspace-wide `.deepnote` file reads sequentially inside the `for` loop, which makes rename/duplicate/create flows slow. Update the file-processing logic in `deepnoteExplorerView` to read and inspect sibling files in parallel using `Promise.all` (or equivalent concurrency) while preserving the existing `isSnapshotFile`, `projectData.project.id`, and `excludeName` filtering, and keep per-file error handling/logging intact for `readDeepnoteProjectFile`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/e2e/suite/initOnlyFile.e2e.test.ts`:
- Line 156: The deleted-toast assertion is hardcoded to “Bootstrap” and can
drift from the rest of the test if the fixture name changes. Update the
notification check in the `waitForNotification` call within the e2e test to use
`NOTEBOOK_NAME` so it stays aligned with the notebook name used elsewhere in
`initOnlyFile.e2e.test.ts`.
---
Outside diff comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 668-691: The Active notebook fallback message is duplicated in
both the not-found branch and the catch block, so extract that repeated
`window.showInformationMessage` logic into a small helper in
`DeepnoteExplorerView` and call it from both places. Keep the existing message
text and metadata formatting the same, but centralize it so the
`treeDataProvider.findTreeItem` and `treeView.reveal` flow stays DRY and avoids
drift.
- Around line 955-981: The “Import deepnote files” loop in deepnoteExplorerView
should use per-iteration error handling so one failed read/write does not abort
the rest of the imports or skip the subsequent Jupyter conversion step. Wrap
each iteration around the workspace.fs.readFile/workspace.fs.writeFile flow in a
try/catch, mirror the isolation already used by
convertJupyterUrisToDeepnoteFiles, and continue processing remaining
deepnoteUris while preserving any successful copies and reporting failures
appropriately.
---
Nitpick comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 444-484: `collectNotebookNamesForProject` is doing workspace-wide
`.deepnote` file reads sequentially inside the `for` loop, which makes
rename/duplicate/create flows slow. Update the file-processing logic in
`deepnoteExplorerView` to read and inspect sibling files in parallel using
`Promise.all` (or equivalent concurrency) while preserving the existing
`isSnapshotFile`, `projectData.project.id`, and `excludeName` filtering, and
keep per-file error handling/logging intact for `readDeepnoteProjectFile`.
🪄 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: CHILL
Plan: Pro
Run ID: aae9a710-ee6e-4b3a-92de-fb8bf05951a9
📒 Files selected for processing (7)
src/notebooks/deepnote/deepnoteExplorerView.tssrc/notebooks/deepnote/deepnoteExplorerView.unit.test.tssrc/notebooks/deepnote/deepnoteKernelAutoSelector.node.tssrc/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.tssrc/notebooks/deepnote/deepnoteTreeDataProvider.tstest/e2e/suite/initOnlyFile.e2e.test.tstest/e2e/suite/revealInExplorer.e2e.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/notebooks/deepnote/deepnoteTreeDataProvider.ts
- src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
- src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
The deleted-toast regex hardcoded "Bootstrap"; build it from NOTEBOOK_NAME so it can't drift from the fixture name used everywhere else in the test. Runtime- identical (CodeRabbit nit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2CLu8UmD8ceGNyv8u96pQ
Comment-only pass over the last review-response commits: drop test-intent preambles that restate test names, review-finding codenames (F2/F3), and the stale implementation pseudocode in the UT-2 doc test; condense the remaining comments to at most two lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
Second comment pass over every added/changed line in the PR (multi-agent scan): remove commented-out code, narration that restates adjacent code or test names, a planning leftover, and reviewer-oriented framing; condense every kept comment (incl. e2e suite headers and public JSDoc) to at most two lines; drop plan-section references (§0/§2/§3/§7) that are unfollowable outside the review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
Reverts a local-dev convenience (commented-out Build preLaunchTask) that should not ship in the PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
Reverts cspell.json to the base word list. basenames/initmain/Résumé become
filenames, init-main.deepnote, and Cliché in the affected tests ('unparseable'
had no remaining usage); the CI failure ('unregisters') is reworded to 'drops'.
Spell check now passes with zero dictionary changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
Restores base comments everywhere the PR reworded, condensed, or deleted comments in functions whose code is identical to main (multi-agent audit): deepnoteServerStarter and IDeepnoteServerStarter JSDoc, serviceAccountValidation, an integrationWebview call-site note, two explorerView JSDoc blocks, three kernel auto-selector field comments, and the e2e helpers/constants/helloWorld trims. Legit doc updates that track real code changes are kept. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
… one seam Replace the per-site 'as any' casts and eslint suppressions with a structural mirror of DeepnoteServerStarter's private surface (typed with the real DeepnoteServerInfo/IDisposable/PendingOperation shapes) behind a single internals() accessor, following the serverHandles() pattern in the kernel auto-selector test. Private-method calls, the sinon isServerRunning stub, and map assertions are now compile-checked; the redundant dynamic vscode imports are gone. Zero eslint-disable comments remain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
…tests Extend the internals() single-seam pattern to ten more test files: structural mirrors typed with real production types for private access (explorer view, snapshot service, tree provider, integration webview), typed builders for VS Code mock shapes (WorkspaceFolder, FileStat, NotebookDocument/Editor, events), and satisfies/as-const for data-file literals. Deletes every as any, : any, as never, and test eslint-disable outside documented ts-mockito seams. The casts were hiding real defects, now fixed: an assertion on a nonexistent serializer field (always false), an openFile test whose raw 'ProjectFile' string never matched the enum so it exercised the early-return path, and block literals missing the required metadata field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/notebooks/deepnote/snapshots/snapshotService.unit.test.ts`:
- Around line 1579-1580: The mock in snapshotService.unit.test.ts is too broad
because getProjectForNotebook is stubbed with anything(), anything(), which
won’t catch incorrect project/notebook lookups from performSnapshotSave().
Update the mock to use the exact expected notebook pair in both affected test
cases so the test verifies the notebook-scoped contract through
IDeepnoteNotebookManager.getProjectForNotebook and fails if the wrong
identifiers are passed.
🪄 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: CHILL
Plan: Pro
Run ID: e583365e-31bc-4645-9d19-3e2ebbb578a2
📒 Files selected for processing (10)
src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.tssrc/notebooks/deepnote/deepnoteExplorerView.unit.test.tssrc/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.tssrc/notebooks/deepnote/deepnoteMultiNotebookSplitter.unit.test.tssrc/notebooks/deepnote/deepnoteNotebookFileFactory.unit.test.tssrc/notebooks/deepnote/deepnoteNotebookInfoStatusBar.unit.test.tssrc/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.tssrc/notebooks/deepnote/integrations/integrationWebview.unit.test.tssrc/notebooks/deepnote/snapshots/snapshotService.unit.test.tssrc/platform/deepnote/deepnoteProjectIdResolver.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- src/notebooks/deepnote/integrations/integrationWebview.unit.test.ts
- src/notebooks/deepnote/deepnoteNotebookFileFactory.unit.test.ts
- src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.unit.test.ts
- src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
- src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts
- src/platform/deepnote/deepnoteProjectIdResolver.unit.test.ts
- src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
- src/notebooks/deepnote/deepnoteMultiNotebookSplitter.unit.test.ts
| const mockNotebookManager = mock<IDeepnoteNotebookManager>(); | ||
| when(mockNotebookManager.getProjectForNotebook(anything(), anything())).thenReturn(originalProject); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Constrain this mock to the exact notebook pair.
anything(), anything() lets the test pass even if performSnapshotSave() asks the manager for the wrong (projectId, notebookId). Stub the expected pair so this covers the notebook-scoped contract.
Proposed fix
- when(mockNotebookManager.getProjectForNotebook(anything(), anything())).thenReturn(originalProject);
+ when(mockNotebookManager.getProjectForNotebook(projectId, notebookId)).thenReturn(originalProject);- when(mockNotebookManager.getProjectForNotebook(anything(), anything())).thenReturn(originalProject);
+ when(mockNotebookManager.getProjectForNotebook(sharedProjectId, 'notebook-b')).thenReturn(originalProject);Also applies to: 1662-1663
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/notebooks/deepnote/snapshots/snapshotService.unit.test.ts` around lines
1579 - 1580, The mock in snapshotService.unit.test.ts is too broad because
getProjectForNotebook is stubbed with anything(), anything(), which won’t catch
incorrect project/notebook lookups from performSnapshotSave(). Update the mock
to use the exact expected notebook pair in both affected test cases so the test
verifies the notebook-scoped contract through
IDeepnoteNotebookManager.getProjectForNotebook and fails if the wrong
identifiers are passed.
…uildSnapshotPath Plan chunk A (items 1,2,5,6,7): make collectNotebookNamesForProject public and call it directly from tests; move buildSnapshotPath into snapshotFiles.ts as an exported pure function (path tests now import it; the Run-All spy becomes a writeFile-URI assertion); drop already-public createSnapshot from the snapshot mirror; export applyVisualFields and test it as a function, replacing one hollow tree test and deleting another that only exercised Map.delete; add the missing tests: no-pending-save guard on document changes, positive SQL env forwarding into runtime-core startServer, per-branch applyVisualFields coverage, and InvalidProjectNameError type guarantees. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
…vice Plan chunk B (item 3): the tracker owns per-notebook cell-execution state (counters, per-cell metadata, error, timestamps) behind a typed API; the service keeps environment capture in its own EnvironmentCaptureState map and delegates the public getters. captureEnvironmentBeforeExecution seeds the session via tracker.ensureExecutionState so startedAt keeps meaning capture time; the Run-All check reads getExecutedBlockCount (number | undefined) so untracked notebooks can never classify as Run-All; clearExecutionState clears both maps. The tracker is an optional last constructor parameter (inject + optional, defaulting internally — no container binding), so tests inject a shared instance and the 42 private record* mirror sites become plain typed calls; the snapshot mirror is down to its six accepted members. New direct tracker suite plus boundary tests: both-sides clear, default-tracker construction, untracked-notebook Run-All corner, capture idempotence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
…o-selector Plan chunk C (item 4): the notebook-to-server-handle map moves behind an injectable get/set registry (symbol + interface in kernels/deepnote/types.ts, Map-backed impl, one addSingleton binding), mirroring the DeepnoteServerProvider precedent. DeepnoteKernelAutoSelector takes the registry as a decorated 17th constructor parameter and its three former private-map sites use it; the private field and the tests' serverHandles() cast helper are deleted — the rebuildController tests seed through the injected registry's public API, and the success path now also asserts the registry ends up tracking the new handle. Adds a small direct registry suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/notebooks/deepnote/snapshots/executionMetadataTracker.unit.test.ts (1)
58-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
assert.deepStrictEqual()instead of checking individual summary properties.Several tests (e.g. Lines 60-63, 106-109) assert multiple properties of the same
metadata.summary/metadataobject one-by-one. As per coding guidelines, "Useassert.deepStrictEqual()for object comparisons instead of checking individual properties."♻️ Example for Lines 58-64
const metadata = tracker.getExecutionMetadata(notebookUri); assert.isDefined(metadata); - assert.strictEqual(metadata!.summary!.blocksExecuted, 2); - assert.strictEqual(metadata!.summary!.blocksSucceeded, 1); - assert.strictEqual(metadata!.summary!.blocksFailed, 1); + assert.deepStrictEqual(metadata!.summary, { + blocksExecuted: 2, + blocksFailed: 1, + blocksSucceeded: 1, + totalDurationMs: metadata!.summary!.totalDurationMs + }); assert.strictEqual(metadata!.error!.name, 'ValueError');Also applies to: 98-110
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/notebooks/deepnote/snapshots/executionMetadataTracker.unit.test.ts` around lines 58 - 64, The tests around execution metadata are asserting object fields one-by-one instead of comparing the full expected structure. Update the affected assertions in executionMetadataTracker.unit.test.ts to use assert.deepStrictEqual() for the metadata.summary and metadata objects, covering the cases near the existing getExecutionMetadata() checks so the expectations are expressed as whole-object comparisons rather than individual properties.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/notebooks/deepnote/snapshots/executionMetadataTracker.unit.test.ts`:
- Around line 58-64: The tests around execution metadata are asserting object
fields one-by-one instead of comparing the full expected structure. Update the
affected assertions in executionMetadataTracker.unit.test.ts to use
assert.deepStrictEqual() for the metadata.summary and metadata objects, covering
the cases near the existing getExecutionMetadata() checks so the expectations
are expressed as whole-object comparisons rather than individual properties.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 10e2024d-070e-42df-99d6-8002fab02ee3
📒 Files selected for processing (16)
src/kernels/deepnote/deepnoteServerHandleRegistry.node.tssrc/kernels/deepnote/deepnoteServerHandleRegistry.unit.test.tssrc/kernels/deepnote/deepnoteServerStarter.unit.test.tssrc/kernels/deepnote/types.tssrc/notebooks/deepnote/deepnoteExplorerView.tssrc/notebooks/deepnote/deepnoteExplorerView.unit.test.tssrc/notebooks/deepnote/deepnoteKernelAutoSelector.node.tssrc/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.tssrc/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.tssrc/notebooks/deepnote/deepnoteTreeItem.tssrc/notebooks/deepnote/snapshots/executionMetadataTracker.tssrc/notebooks/deepnote/snapshots/executionMetadataTracker.unit.test.tssrc/notebooks/deepnote/snapshots/snapshotFiles.tssrc/notebooks/deepnote/snapshots/snapshotService.tssrc/notebooks/deepnote/snapshots/snapshotService.unit.test.tssrc/notebooks/serviceRegistry.node.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- src/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.ts
- src/notebooks/deepnote/deepnoteTreeItem.ts
- src/kernels/deepnote/deepnoteServerStarter.unit.test.ts
- src/notebooks/deepnote/snapshots/snapshotService.unit.test.ts
- src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
- src/notebooks/deepnote/snapshots/snapshotService.ts
- src/notebooks/deepnote/deepnoteExplorerView.ts
Addresses CodeRabbit's nitpick on the ExecutionMetadataTracker suite: compare metadata.summary as a whole object (repo guideline) instead of field-by-field; the controlled timestamps make totalDurationMs deterministic, so the full shape is pinned exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T52xcYnMMDH4xV3ERdwjKN
Migrates the extension to a single notebook per
.deepnotefile model, removing the multi-notebook-per-file machinery (the?notebook=<id>URI selection and its timers/manager state).What changes
(projectId, notebookId)lookup..deepnoteof the project on disk (open or closed), with file-watcher self-write suppression.notebook.uri.toString(), 1:1 with the kernel/controller). Environment deletion now actually stops every server using it (including closed-but-running ones), fixing a real bug.WeakSet<IKernel>, not a persistent project flag).How it was built
Implemented in 7 sequential, independently-reviewed chunks (8 commits). Each chunk was implemented, reviewed against the plan, unit-tested against the plan's use cases, and committed green.
Verification
tscclean, both esbuild bundles build,prettier --checkclean.TextBlockConvertertest and 3cloud-sqltscerrors are pre-existing local symlink drift (the@deepnote/*packages are ahead of the repo locally; CI uses the published versions) — not introduced by this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01URccsVKXeNKZqqPi89L4ro
Summary by CodeRabbit