Skip to content

feat(deepnote): one notebook per .deepnote file#429

Draft
tkislan wants to merge 80 commits into
mainfrom
tk/single-notebook
Draft

feat(deepnote): one notebook per .deepnote file#429
tkislan wants to merge 80 commits into
mainfrom
tk/single-notebook

Conversation

@tkislan

@tkislan tkislan commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Migrates the extension to a single notebook per .deepnote file model, removing the multi-notebook-per-file machinery (the ?notebook=<id> URI selection and its timers/manager state).

What changes

  • Deserialize renders one notebook — the first non-init notebook (falling back to the init only when it is the file's only notebook); serialize resolves the target from document metadata with an exact (projectId, notebookId) lookup.
  • Legacy multi-notebook files are split on demand — opening one shows a notification that writes one new single-notebook file per notebook, migrates the environment selection, then deletes the original (write-before-delete; the original is never lost on failure).
  • New / duplicated notebooks become sibling files (never appended into one file).
  • Project metadata fans out across siblings — integration/rename edits are written to every sibling .deepnote of the project on disk (open or closed), with file-watcher self-write suppression.
  • Explorer is grouped by project (ProjectGroup → file → notebook) plus a status bar showing the active notebook with a "Copy details" command.
  • Server & environment are keyed per notebook (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.
  • Snapshots are notebook-scoped with a backward-compatible reader (legacy project-scoped snapshots still load as a fallback and are never migrated) and a deferred, output-settled save.
  • Init notebook runs per kernel from its sibling file — re-running after an in-place restart (tracked in a 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

  • tsc clean, both esbuild bundles build, prettier --check clean.
  • Unit suite: 2501 passing, 234 pending.
  • The 1 failing TextBlockConverter test and 3 cloud-sql tsc errors 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

  • New Features

    • Deepnote Explorer now groups .deepnote files by project, enabling notebook-scoped create/duplicate/rename/delete and notebook-only import/export flows.
    • Multi-notebook .deepnote files can be split into collision-safe sibling files, with optional environment migration.
    • Added a Deepnote status bar item to copy the active notebook’s details to the clipboard.
  • Bug Fixes

    • Improved notebook-scoped isolation for SQL environment variables, server lifecycle, integrations, and snapshots.
    • Fixed indented bullet rendering and strengthened legacy + notebook-scoped snapshot restore.
    • Safer environment deletion: running servers are stopped before mappings are removed, with failure-tolerant cleanup.

tkislan and others added 8 commits June 23, 2026 22:23
…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

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0%. Comparing base (6363487) to head (9d0a0fa).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #429   +/-   ##
===========================
===========================
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

tkislan and others added 21 commits June 24, 2026 06:54
- 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
tkislan and others added 4 commits July 8, 2026 06:00
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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 14769f9 and 5c74813.

📒 Files selected for processing (10)
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteMultiNotebookSplitter.unit.test.ts
  • src/notebooks/deepnote/deepnoteNotebookFileFactory.unit.test.ts
  • src/notebooks/deepnote/deepnoteNotebookInfoStatusBar.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
  • src/notebooks/deepnote/integrations/integrationWebview.unit.test.ts
  • src/notebooks/deepnote/snapshots/snapshotService.unit.test.ts
  • src/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

Comment thread src/notebooks/deepnote/snapshots/snapshotService.unit.test.ts Outdated
tkislan and others added 3 commits July 9, 2026 12:04
…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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/notebooks/deepnote/snapshots/executionMetadataTracker.unit.test.ts (1)

58-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use 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/metadata object one-by-one. As per coding guidelines, "Use assert.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

📥 Commits

Reviewing files that changed from the base of the PR and between 5c74813 and ea42807.

📒 Files selected for processing (16)
  • src/kernels/deepnote/deepnoteServerHandleRegistry.node.ts
  • src/kernels/deepnote/deepnoteServerHandleRegistry.unit.test.ts
  • src/kernels/deepnote/deepnoteServerStarter.unit.test.ts
  • src/kernels/deepnote/types.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeItem.ts
  • src/notebooks/deepnote/snapshots/executionMetadataTracker.ts
  • src/notebooks/deepnote/snapshots/executionMetadataTracker.unit.test.ts
  • src/notebooks/deepnote/snapshots/snapshotFiles.ts
  • src/notebooks/deepnote/snapshots/snapshotService.ts
  • src/notebooks/deepnote/snapshots/snapshotService.unit.test.ts
  • src/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

tkislan and others added 14 commits July 9, 2026 16:05
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
…dule

The @deepnote/runtime-core mock lived as an untyped JS string inside
mocha-esm-loader.js, and tests reached its __ helpers through a dynamic
import force-cast to a hand-maintained interface.

Move the mock to src/test/mocks/deepnoteRuntimeCore.ts with exports typed
against the real package (RuntimeCore['startServer'] etc.), so the compiler
catches drift from the actual API. The loader now resolves the bare
specifier to the compiled mock file, so the code under test and the tests
importing the helpers share one module instance — the cast, the mirror
interface, and the async accessor are all gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRc87PBXaWnx2WnhQQ842J
…ls() mirrors

Replace the hand-written structural-mirror interfaces and their `as unknown as`
internals() accessors with each class's real entry points, so a private rename
can no longer silently break (or vacuously pass) a test:

- server starter: public startServer/stopServer + the recorded runtime-core mock
- integration webview: the real `save` webview message
- snapshot service: real execution events (notifyQueueComplete / changeCellState)
  observed at the public createSnapshot seam, via one shared activation fixture
- explorer view: captured command handlers invoked through handlerFor(Commands.X)

Also make the vscode-mock EventEmitter honor its `disposables` argument so an
activated service's subscriptions are disposed between tests. Drops the private
performSnapshotSave stubs / updateLatestSnapshot spies, hand-built change events,
and three hollow DI-identity tests. The small tree-provider mirror is kept
deliberately — removing it would add more scaffolding than it removes.

Full unit suite green (0 failing); ESLint clean on the touched files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
… e2e assertNotNull

Working-tree changes authored outside this task, committed together at the
user's request:

- deepnoteServerUtils: rename the param to deepnoteFileUri and key the server
  config handle by fsPath. Every caller goes through this single helper, so the
  format stays consistent (no hand-built handle strings elsewhere).
- platform types: getProjectForNotebook now returns the canonical DeepnoteFile
  from @deepnote/blocks instead of the local DeepnoteProject alias.
- e2e: add an assertNotNull(value, message) helper and adopt it across the
  suites, replacing `expect(x).to.not.equal(undefined)` + `x!` non-null
  assertions with a single guard that fails loudly and narrows the type.

The project type-checks and the unit suite is green. The e2e suites were not
run here (they require the ExTester/xvfb harness).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
…tarter calls

Updated unit tests for DeepnoteServerStarter to directly invoke serverStarter.startServer instead of using a separate start function. This change improves clarity and consistency in the test suite by eliminating the intermediate function. All relevant test cases have been updated accordingly to ensure functionality remains intact.
Refactored the Deepnote tree item structure to encapsulate additional data within an `extra` property, enhancing type safety and clarity. Updated related methods and tests to accommodate this change, ensuring consistent handling of tree item types across the codebase. This refactor improves maintainability and aligns with the new data model for Deepnote items.
The DeepnoteKernelStatusIndicator service and its duplicate .node.ts copy
were added in #152 but never registered in either service registry, so
the service never activated. There are zero references anywhere in the
codebase, and the two copies had drifted (the .ts had status-bar theme
colors and a reveal command the .node.ts lacked). Remove both dead files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
…tebookKey utility

Updated various files to utilize the new getNotebookKey function for consistent notebook key generation. This change enhances code clarity and maintainability by centralizing the logic for obtaining notebook keys, ensuring uniformity across the codebase. Adjustments were made in the DeepnoteLspClientManager, ServerHandleRegistry, and several other components to reflect this update.
Removed the IDeepnoteInitNotebookRunner interface and updated the DeepnoteInitNotebookRunner class to directly implement IExtensionSyncActivationService. Adjusted service registration in serviceRegistry to reflect this change, enhancing clarity and reducing complexity in the codebase.
…port shim

The notebooks-layer deepnoteProjectUtils re-exported readDeepnoteProjectFile
and getNotebookKey from the platform layer. Remove those pass-through
re-exports and point every caller at the canonical platform modules
(deepnoteProjectFileReader, deepnoteProjectUtils) so there is a single source
of truth for reading and keying `.deepnote` files. computeRequirementsHash
stays in the notebooks-layer util.

The obsolete deepnoteProjectUtils.unit.test.ts only exercised the removed
readDeepnoteProjectFile re-export; that behavior is already covered by
deepnoteProjectFileReader.unit.test.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
Replace the inline workspace.fs.readFile + TextDecoder + deserializeDeepnoteFile
reads in SnapshotService with the canonical readDeepnoteProjectFile helper, so
snapshot reads go through the same single-source-of-truth parser used
elsewhere. Drops the now-unused deserializeDeepnoteFile import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
notebookCount now parses the `.deepnote` YAML with deserializeDeepnoteFile
(the same schema-validating parser used to deserialize deepnote files) and
returns project.notebooks.length, rather than counting "- blocks:" lines with
a regex. Verified against the split e2e suite (5 passing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/notebooks/deepnote/deepnoteInitNotebookRunner.node.ts (1)

78-94: 🩺 Stability & Availability | 🟠 Major

Prevent overlapping init executions per kernel.

inFlightInitByKernel is populated only after asynchronous discovery, so two start events can both pass the Line 79 guard and later execute init concurrently. On restart, Line 91 cancels the prior token but Line 92 starts the replacement immediately; cancellation is only checked between blocks, so executeHidden calls can overlap. Reserve the run before the first await, and await or serialize the cancelled run before starting its replacement.

Also applies to: 140-164

🤖 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.ts` around lines 78 -
94, Prevent concurrent initialization in onDidStartKernel and onDidRestartKernel
by reserving the kernel’s in-flight run before any asynchronous discovery
begins. On restart, cancel the existing run and await its completion or
otherwise serialize it before invoking runInitForKernel again, ensuring
executeHidden calls cannot overlap; update the related logic around the
additional run path at lines 140-164 consistently.
🤖 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.

Duplicate comments:
In `@src/notebooks/deepnote/deepnoteInitNotebookRunner.node.ts`:
- Around line 78-94: Prevent concurrent initialization in onDidStartKernel and
onDidRestartKernel by reserving the kernel’s in-flight run before any
asynchronous discovery begins. On restart, cancel the existing run and await its
completion or otherwise serialize it before invoking runInitForKernel again,
ensuring executeHidden calls cannot overlap; update the related logic around the
additional run path at lines 140-164 consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fdfe43a2-a84e-4527-af51-5405d0ea9165

📥 Commits

Reviewing files that changed from the base of the PR and between 818bde0 and 48a3098.

📒 Files selected for processing (25)
  • src/kernels/deepnote/deepnoteLspClientManager.node.ts
  • src/kernels/deepnote/deepnoteServerHandleRegistry.node.ts
  • src/kernels/deepnote/deepnoteServerStarter.unit.test.ts
  • src/kernels/deepnote/types.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.ts
  • src/notebooks/deepnote/deepnoteInitNotebookRunner.node.ts
  • src/notebooks/deepnote/deepnoteInputBlockEditProtection.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteKernelStatusIndicator.node.ts
  • src/notebooks/deepnote/deepnoteKernelStatusIndicator.ts
  • src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts
  • src/notebooks/deepnote/deepnoteProjectUtils.ts
  • src/notebooks/deepnote/deepnoteProjectUtils.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeItem.ts
  • src/notebooks/deepnote/deepnoteTreeItem.unit.test.ts
  • src/notebooks/deepnote/snapshots/snapshotService.ts
  • src/notebooks/serviceRegistry.node.ts
  • src/platform/deepnote/deepnoteProjectIdResolver.ts
  • src/platform/deepnote/deepnoteProjectUtils.ts
  • test/e2e/helpers/yaml.ts
💤 Files with no reviewable changes (4)
  • src/notebooks/deepnote/deepnoteProjectUtils.unit.test.ts
  • src/notebooks/deepnote/deepnoteKernelStatusIndicator.node.ts
  • src/notebooks/deepnote/deepnoteProjectUtils.ts
  • src/notebooks/deepnote/deepnoteKernelStatusIndicator.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/kernels/deepnote/deepnoteServerHandleRegistry.node.ts
  • src/kernels/deepnote/types.ts
  • src/platform/deepnote/deepnoteProjectIdResolver.ts
  • src/notebooks/deepnote/deepnoteTreeItem.ts
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.ts
  • src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/kernels/deepnote/deepnoteServerStarter.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.ts
  • src/notebooks/deepnote/snapshots/snapshotService.ts

…eFile

Replace every usage of the deprecated `DeepnoteProject` type alias with
`DeepnoteFile` imported directly from `@deepnote/blocks`, then remove the
now-unused re-export from deepnoteTypes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/notebooks/deepnote/deepnoteNotebookManager.ts (1)

52-52: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clone integrations per cached notebook.

Line [52] assigns the caller-owned mutable array to every cached sibling. Mutating one cached project—or the original array—can then change all siblings and persist incorrect metadata.

Suggested fix
-            updatedProject.project.integrations = integrations;
+            updatedProject.project.integrations = structuredClone(integrations);
🤖 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` at line 52, Update the
assignment in the cached notebook/project update flow so each cached sibling
receives an independent clone of integrations rather than the caller-owned
array. Preserve the integration contents while preventing mutations to one
project or the original input from affecting other cached projects.
🤖 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.

Outside diff comments:
In `@src/notebooks/deepnote/deepnoteNotebookManager.ts`:
- Line 52: Update the assignment in the cached notebook/project update flow so
each cached sibling receives an independent clone of integrations rather than
the caller-owned array. Preserve the integration contents while preventing
mutations to one project or the original input from affecting other cached
projects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a9b8d18-6abc-425f-afe7-ec2a6891c114

📥 Commits

Reviewing files that changed from the base of the PR and between 48a3098 and f732f8f.

📒 Files selected for processing (14)
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.ts
  • src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteNotebookManager.ts
  • src/notebooks/deepnote/deepnoteNotebookManager.unit.test.ts
  • src/notebooks/deepnote/deepnoteRequirementsHelper.node.ts
  • src/notebooks/deepnote/deepnoteRequirementsHelper.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteSerializer.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeItem.unit.test.ts
  • src/notebooks/deepnote/integrations/federatedAuth/federatedAuthTestHelpers.ts
  • src/notebooks/types.ts
  • src/platform/deepnote/deepnoteTypes.ts
  • src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts
💤 Files with no reviewable changes (1)
  • src/platform/deepnote/deepnoteTypes.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/notebooks/deepnote/deepnoteNotebookManager.unit.test.ts
  • src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts
  • src/notebooks/deepnote/deepnoteInitNotebookRunner.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.unit.test.ts
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeItem.unit.test.ts
  • src/notebooks/deepnote/deepnoteTreeDataProvider.ts
  • src/notebooks/deepnote/deepnoteSerializer.unit.test.ts

…d-reference leaks

updateProjectIntegrations assigned the caller-owned integrations array by
reference into every cached sibling, so mutating one cached project — or the
caller mutating its input after the call — leaked across all cached projects.
Deep-clone the array per entry so each sibling is independent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
@tkislan

tkislan commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

getProjectForNotebook was stubbed with anything(), anything() across the
snapshot save-path tests, so a wrong project/notebook lookup from the save
path would go undetected. Stub the exact (projectId, notebookId) pair each
test saves, so the notebook-scoped contract is verified and a mismatched
lookup now fails the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant