Skip to content

Conversation

@jamesbhobbs
Copy link
Contributor

@jamesbhobbs jamesbhobbs commented Oct 14, 2025

chore: sync biome and prettier configs from deepnote/deepnote (GRN-4938)

Summary

This PR syncs the linting and formatting tooling from the main deepnote/deepnote repository to jupyterlab-deepnote, replacing ESLint with Biome while adapting for jupyterlab's specific needs (uses jlpm instead of pnpm).

Key changes:

  • Replaced ESLint with Biome 2.2.5 for TypeScript/JavaScript/JSON linting & formatting
  • Updated Prettier to 3.6.2, now only handles Markdown/YAML (Biome handles TS/JS/JSON)
  • Kept stylelint for CSS (Biome doesn't support CSS)
  • Applied code formatting across the codebase (single quotes, ES5 trailing commas, semicolons as needed)
  • Updated tooling configs: Added biome.json, .prettierrc, updated lint-staged, removed inline ESLint/Prettier configs from package.json

⚠️ Note on .gitignore diff: The lint-staged config includes sort -o .gitignore .gitignore, which alphabetically sorted the entire .gitignore file. This creates a noisy diff but has no functional impact.

Review & Testing Checklist for Human

  • Test extension end-to-end in JupyterLab - Install the built extension and verify .deepnote files open and display correctly (this is the most critical check since only unit tests were run)
  • Review removed console statements - Biome removed console.error/log calls (e.g., in transform-deepnote-yaml-to-notebook-content.ts:49). Verify these weren't critical for debugging production issues
  • Verify CI passes completely - Ensure all lint, typecheck, and test jobs pass in CI
  • Spot check formatting changes - Review a few files to confirm the new code style (single quotes, ES5 trailing commas) is acceptable
  • Review .gitignore reorganization - The file was alphabetically sorted by lint-staged, creating a large diff. Verify all necessary patterns are still present

Recommended test plan:

  1. Build the extension locally: jlpm build:prod
  2. Install in JupyterLab: jupyter labextension develop . --overwrite
  3. Open a .deepnote file in JupyterLab and verify it renders correctly
  4. Test switching between notebooks using the picker dropdown
  5. Verify pre-commit hooks work: make a small change and commit to trigger lint-staged

Notes

  • Biome rule useImportType had to be ignored for React import in NotebookPicker.tsx because tsconfig uses jsx: "react" which requires React as a value (not just a type)
  • Prettier config changed: trailingComma: "es5" (was "none"), arrowParens: "asNeeded" (was "avoid")
  • No packageManager field in package.json (jupyterlab uses jlpm, not pnpm)

Linear: GRN-4938
Session: https://app.devin.ai/sessions/0ebe242fb2894c6493136cd67ae4ff34
Requested by: James Hobbs (@jamesbhobbs, [email protected])

Summary by CodeRabbit

  • Style

    • Standardized quoting and semicolon usage; broad formatting cleanups across code, styles, and configs without changing behavior.
  • Chores

    • Adopted a new lint/format toolchain and updated scripts.
    • Expanded and normalized ignore rules.
    • Added focused Prettier configuration for YAML.
    • Minor CI workflow formatting adjustments.
  • Refactor

    • Type-only imports and simplified mappings to reduce runtime overhead; no user-visible changes.
  • Tests

    • Formatting-only updates to test files; test behavior unchanged.

- Add [email protected] for TypeScript/JavaScript/JSON linting & formatting
- Update prettier to 3.6.2 for Markdown/YAML formatting only
- Remove ESLint dependencies and configuration
- Add new scripts: biome:check, biome:check:fix, lintAndFormat, lintAndFormat:fix
- Update lint-staged to use biome for TS/JS/JSON, prettier for MD/YAML, stylelint for CSS
- Copy biome.json, .prettierrc, .prettierignore from main repo
- Update .gitignore to exclude .biome/ cache
- Apply biome formatting to codebase (single quotes, ES5 trailing commas, semicolons as needed)
- Keep stylelint for CSS (biome doesn't support CSS)
- Maintain jlpm compatibility (no packageManager field)
@linear
Copy link

linear bot commented Oct 14, 2025

@devin-ai-integration
Copy link

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

📝 Walkthrough

Walkthrough

Repository-wide tooling and formatting updates migrate linting from ESLint to Biome, introducing biome.json and updating package.json scripts, devDependencies, and lint-staged. Prettier is configured via .prettierrc to enforce single quotes in YAML; .prettierignore is narrowed. .gitignore is expanded and reorganized. Multiple files receive formatting-only edits (semicolons, quotes, imports as type-only). GitHub Actions workflows get quoting/spacing tweaks. Minor behavioral adjustments remove console.error logging in a few paths and slightly refactor response parsing in src/handler.ts, with no public API changes.

Possibly related PRs

Suggested reviewers

  • Artmann
  • saltenasl

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary change of synchronizing Biome and Prettier configurations from the upstream deepnote repository and references the associated issue, aligning directly with the PR’s main purpose.
Linked Issues Check ✅ Passed This PR fully implements GRN-4938 by replacing ESLint with Biome, updating Prettier to handle only Markdown and YAML, retaining stylelint for CSS, adding biome.json and .prettierrc, and adjusting scripts and lint-staged rules to match the objectives.
Out of Scope Changes Check ✅ Passed All changes consist of configuration updates and code formatting refinements that directly support syncing linting and formatting tooling, with no unrelated features or logic alterations observed.

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

@codecov
Copy link

codecov bot commented Oct 14, 2025

Codecov Report

❌ Patch coverage is 0% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 19.07%. Comparing base (36431a2) to head (acc9835).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/components/NotebookPicker.tsx 0.00% 23 Missing ⚠️
src/index.ts 0.00% 16 Missing ⚠️
src/handler.ts 0.00% 13 Missing ⚠️
src/convert-deepnote-block-to-jupyter-cell.ts 0.00% 12 Missing ⚠️
src/deepnote-content-provider.ts 0.00% 12 Missing ⚠️
src/transform-deepnote-yaml-to-notebook-content.ts 0.00% 9 Missing ⚠️
src/convert-deepnote-block-type-to-jupyter.ts 0.00% 2 Missing ⚠️
src/types.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #28      +/-   ##
==========================================
+ Coverage   18.50%   19.07%   +0.57%     
==========================================
  Files          13       13              
  Lines         200      194       -6     
  Branches       27       24       -3     
==========================================
  Hits           37       37              
+ Misses        163      157       -6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36431a2 and acc9835.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (27)
  • .copier-answers.yml (0 hunks)
  • .github/workflows/ci.yml (1 hunks)
  • .github/workflows/prep-release.yml (2 hunks)
  • .github/workflows/publish-release.yml (2 hunks)
  • .github/workflows/update-integration-tests.yml (0 hunks)
  • .gitignore (1 hunks)
  • .prettierignore (1 hunks)
  • .prettierrc (1 hunks)
  • babel.config.js (1 hunks)
  • biome.json (1 hunks)
  • jest.config.js (2 hunks)
  • package.json (4 hunks)
  • renovate.json (1 hunks)
  • src/__tests__/jupyterlab_deepnote.spec.ts (1 hunks)
  • src/components/NotebookPicker.tsx (2 hunks)
  • src/convert-deepnote-block-to-jupyter-cell.ts (1 hunks)
  • src/convert-deepnote-block-type-to-jupyter.ts (1 hunks)
  • src/deepnote-content-provider.ts (1 hunks)
  • src/fallback-data.ts (2 hunks)
  • src/handler.ts (2 hunks)
  • src/index.ts (3 hunks)
  • src/transform-deepnote-yaml-to-notebook-content.ts (1 hunks)
  • src/types.ts (1 hunks)
  • style/index.css (1 hunks)
  • style/index.js (1 hunks)
  • ui-tests/playwright.config.js (1 hunks)
  • ui-tests/tests/jupyterlab_deepnote.spec.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • .github/workflows/update-integration-tests.yml
  • .copier-answers.yml
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
PR: deepnote/deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-14T13:43:36.344Z
Learning: Applies to **/*.{ts,tsx} : Follow Biome's lint rules as configured in biome.json
Learnt from: FilipPyrek
PR: deepnote/deepnote#18531
File: biome.json:58-58
Timestamp: 2025-09-25T14:43:13.976Z
Learning: Biome is a JavaScript/TypeScript linter and formatter that doesn't process .proto files, so Protocol Buffer source files don't need to be explicitly ignored in biome.json configuration.
📚 Learning: 2025-10-14T13:43:36.344Z
Learnt from: CR
PR: deepnote/deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-14T13:43:36.344Z
Learning: Applies to **/*.{md,yml,yaml} : Use Prettier formatting for Markdown and YAML files

Applied to files:

  • .prettierrc
📚 Learning: 2025-09-25T14:43:13.976Z
Learnt from: FilipPyrek
PR: deepnote/deepnote#18531
File: biome.json:58-58
Timestamp: 2025-09-25T14:43:13.976Z
Learning: Biome is a JavaScript/TypeScript linter and formatter that doesn't process .proto files, so Protocol Buffer source files don't need to be explicitly ignored in biome.json configuration.

Applied to files:

  • biome.json
📚 Learning: 2025-10-14T13:43:36.344Z
Learnt from: CR
PR: deepnote/deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-14T13:43:36.344Z
Learning: Applies to **/*.{ts,tsx} : Follow Biome's lint rules as configured in biome.json

Applied to files:

  • biome.json
  • package.json
📚 Learning: 2025-10-09T11:21:57.494Z
Learnt from: CR
PR: deepnote/vscode-deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-09T11:21:57.494Z
Learning: Applies to src/notebooks/deepnote/deepnoteTypes.ts : deepnoteTypes.ts contains Deepnote-related type definitions

Applied to files:

  • src/types.ts
  • src/fallback-data.ts
  • src/convert-deepnote-block-to-jupyter-cell.ts
📚 Learning: 2025-10-09T11:21:57.494Z
Learnt from: CR
PR: deepnote/vscode-deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-09T11:21:57.494Z
Learning: Applies to src/notebooks/deepnote/deepnoteDataConverter.ts : deepnoteDataConverter.ts performs Deepnote data transformations

Applied to files:

  • src/types.ts
  • src/deepnote-content-provider.ts
  • src/convert-deepnote-block-type-to-jupyter.ts
  • src/convert-deepnote-block-to-jupyter-cell.ts
  • src/transform-deepnote-yaml-to-notebook-content.ts
📚 Learning: 2025-10-09T11:21:57.494Z
Learnt from: CR
PR: deepnote/vscode-deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-09T11:21:57.494Z
Learning: Applies to src/notebooks/deepnote/deepnoteSerializer.ts : deepnoteSerializer.ts is the main serializer orchestrating Deepnote integration

Applied to files:

  • src/types.ts
📚 Learning: 2025-10-09T11:21:57.494Z
Learnt from: CR
PR: deepnote/vscode-deepnote#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-09T11:21:57.494Z
Learning: Applies to src/notebooks/deepnote/deepnoteNotebookSelector.ts : deepnoteNotebookSelector.ts implements UI selection logic for Deepnote notebooks

Applied to files:

  • src/components/NotebookPicker.tsx
📚 Learning: 2025-09-03T12:59:14.489Z
Learnt from: CR
PR: deepnote/vscode-extension#0
File: .github/instructions/notebooks.instructions.md:0-0
Timestamp: 2025-09-03T12:59:14.489Z
Learning: Applies to src/notebooks/export/**/*.ts : Add UI elements for the new format to the export quick pick

Applied to files:

  • src/components/NotebookPicker.tsx
📚 Learning: 2025-09-14T18:25:57.101Z
Learnt from: jamesbhobbs
PR: deepnote/deepnote#18401
File: scripts/check-chromatic-enabled/pr-template.test.ts:1-6
Timestamp: 2025-09-14T18:25:57.101Z
Learning: In the deepnote/deepnote repository, colocated tests use Node's built-in test runner (node:test, node:assert/strict) rather than Jest.

Applied to files:

  • src/__tests__/jupyterlab_deepnote.spec.ts
🧬 Code graph analysis (4)
src/index.ts (2)
src/deepnote-content-provider.ts (2)
  • DeepnoteContentProvider (18-48)
  • deepnoteContentProviderName (5-5)
src/components/NotebookPicker.tsx (1)
  • NotebookPicker (8-93)
src/components/NotebookPicker.tsx (1)
src/types.ts (1)
  • deepnoteMetadataSchema (5-14)
src/deepnote-content-provider.ts (1)
src/transform-deepnote-yaml-to-notebook-content.ts (1)
  • transformDeepnoteYamlToNotebookContent (6-51)
src/convert-deepnote-block-to-jupyter-cell.ts (1)
src/convert-deepnote-block-type-to-jupyter.ts (1)
  • convertDeepnoteBlockTypeToJupyter (1-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: check_release
🔇 Additional comments (22)
.prettierignore (1)

1-2: Verify removed ignore patterns are safe.

The ignore list was drastically narrowed—previously excluded paths like lib/, package.json, and build artifacts are now formatted. Ensure build outputs are in .gitignore and that formatting package.json aligns with tooling expectations.

.github/workflows/publish-release.yml (1)

1-58: LGTM—formatting only.

String quoting standardized to single quotes throughout; no behavioral changes.

src/fallback-data.ts (2)

1-2: LGTM—correct type-only imports.

ICodeCell and IDeepnoteNotebookContent properly imported as types; runtime dependency eliminated.


9-29: LGTM—formatting.

Trailing commas added; no structural changes.

ui-tests/playwright.config.js (1)

4-14: LGTM—formatting only.

Semicolon removal and trailing commas added; configuration unchanged.

src/convert-deepnote-block-to-jupyter-cell.ts (2)

1-2: LGTM—correct type-only imports.

DeepnoteBlock, ICodeCell, and IMarkdownCell properly imported as types.


7-39: LGTM—formatting.

Semicolons removed, formatting adjusted; logic unchanged.

.github/workflows/prep-release.yml (1)

1-48: LGTM—formatting only.

String quoting standardized to single quotes; no behavioral changes.

src/deepnote-content-provider.ts (2)

5-47: LGTM—formatting and structure preserved.

Aside from error handling concern, the transformation logic remains intact.


1-1: Type-only import of Contents is safe – it’s only used in type positions (IFetchOptions, IModel), so erasing it at compile time won’t affect runtime.

src/transform-deepnote-yaml-to-notebook-content.ts (2)

1-4: LGTM—correct type-only imports.

IDeepnoteNotebookContent and IDeepnoteNotebookMetadata properly imported as types.


6-47: LGTM—formatting and logic preserved.

Semicolons removed, formatting adjusted; transformation logic unchanged.

package.json (2)

33-35: Biome/Prettier integration looks good.

Scripts and devDeps align with the migration; Prettier limited to md/yaml and Biome for TS/JS/JSON is consistent.

Also applies to: 48-55, 75-90, 101-114


111-113: lint-staged: .gitignore sort is not cross‑platform.

The bare sort can fail on Windows shells. Consider a Node-based sorter or shx.

Suggested change:

-        ".gitignore": [
-            "sort -o .gitignore .gitignore"
-        ]
+        ".gitignore": [
+            "node -e \"const fs=require('fs');const l=fs.readFileSync('.gitignore','utf8').split(/\\r?\\n/);l.sort((a,b)=>a.localeCompare(b));fs.writeFileSync('.gitignore', l.join('\\n'));\""
+        ]
src/__tests__/jupyterlab_deepnote.spec.ts (1)

7-9: LGTM (formatting only).

biome.json (1)

4-4: Relocate glob patterns to files.include/ignore
The formatter.includes setting isn’t defined in the Biome 2.2.5 schema; move your include/exclude globs under files.include and files.ignore so they’re applied.

src/handler.ts (2)

28-35: LGTM: dataText refactor improves clarity.

Extracting dataText and checking its length before parsing is clearer than the previous approach. The empty catch for JSON parse errors is appropriate here—non-JSON responses are handled gracefully.


38-39: LGTM: error message extraction is more robust.

Fallback to dataText when errorData.message is unavailable improves error reporting.

src/index.ts (4)

1-7: LGTM: type-only imports follow best practices.

Using type imports for type-only references aligns with TypeScript best practices and Biome configuration.


25-31: LGTM: provider registration is more concise.

Moving serverSettings and apiEndpoint inline and consolidating the registration improves readability without changing behavior.


48-54: LGTM: toolbar factory logic is streamlined.

Conditional rendering for .deepnote files remains intact; returning an empty Widget for non-matching files is appropriate.


22-24: Add logging for missing registry before returning. A silent return on undefined registry may obscure configuration issues; restore a console.error or warning here.

Comment on lines +51 to +204
# Usually these files are written by a python script from a template
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
# before PyInstaller builds the exe, so as to inject date/other infos into it.
# Biome cache
# Biome cache
# Byte-compiled / optimized / DLL files
# Byte-compiled / optimized / DLL files
# C extensions
# C extensions
# Created by https://www.gitignore.io/api/python
# Created by https://www.gitignore.io/api/python
# Distribution / packaging
# Distribution / packaging
# Edit at https://www.gitignore.io/?templates=python
# Edit at https://www.gitignore.io/?templates=python
# End of https://www.gitignore.io/api/python
# End of https://www.gitignore.io/api/python
# Installer logs
# Installer logs
# Integration tests
# Integration tests
# Mr Developer
# Mr Developer
# OSX files
# OSX files
# PyBuilder
# PyBuilder
# PyInstaller
# PyInstaller
# Pyre type checker
# Pyre type checker
# Qlty cache directories
# Qlty cache directories
# Rope project settings
# Rope project settings
# SageMath parsed files
# SageMath parsed files
# Scrapy stuff:
# Scrapy stuff:
# Sphinx documentation
# Sphinx documentation
# Spyder project settings
# Spyder project settings
# Translations
# Translations
# Unit test / coverage reports
# Unit test / coverage reports
# Version file is handled by hatchling
# Version file is handled by hatchling
# Yarn cache
# Yarn cache
# celery beat schedule file
# celery beat schedule file
# mkdocs documentation
# mkdocs documentation
# mypy
# mypy
# pyenv
# pyenv
### Python ###
### Python ###
*$py.class
*$py.class
*.bundle.*
*.bundle.*
*.cover
*.cover
*.egg
*.egg
*.egg-info/
*.egg-info/
*.log
*.log
*.manifest
*.manifest
*.mo
*.mo
*.pot
*.pot
*.py[cod]
*.py[cod]
*.sage.py
*.sage.py
*.so
*.so
*.spec
*.spec
*.tsbuildinfo
*.tsbuildinfo
.DS_Store
.DS_Store
.Python
.Python
.biome/
.biome/
.cache
.cache
.coverage
.coverage
.coverage.*
.coverage.*
.dmypy.json
.dmypy.json
.eggs/
.eggs/
.eslintcache
.eslintcache
.hypothesis/
.hypothesis/
.installed.cfg
.installed.cfg
.ipynb_checkpoints
.ipynb_checkpoints
.mr.developer.cfg
.mr.developer.cfg
.mypy_cache/
.mypy_cache/
.nox/
.nox/
.project
.project
.pydevproject
.pydevproject
.pyre/
.pyre/
.pytest_cache/
.pytest_cache/
.python-version
.python-version
.qlty/cache
.qlty/cache
.qlty/logs
.qlty/logs
.qlty/out
.qlty/out
.qlty/plugin_cachedir
.qlty/plugin_cachedir
.qlty/results
.qlty/results
.ropeproject
.ropeproject
.scrapy
.scrapy
.spyderproject
.spyderproject
.spyproject
.spyproject
.stylelintcache
.stylelintcache
.tox/
.tox/
.yarn/
.yarn/
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Deduplicate the doubled ignore block.

Every comment and pattern here appears twice in a row—looks like the template got pasted twice. Please collapse to single instances to keep the ignore file readable.

🤖 Prompt for AI Agents
In .gitignore around lines 51 to 204 the entire ignore template has been
duplicated (every comment and pattern appears twice); remove the repeated blocks
so each ignore entry and comment appears only once, preserving the original
order and content but eliminating the duplicated consecutive lines to keep the
file concise and readable.

Comment on lines 14 to +21
void panel.context.ready.then(() => {
const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote');
const metadataNames = deepnoteMetadata?.notebook_names;
const names =
Array.isArray(metadataNames) &&
metadataNames.every(n => typeof n === 'string')
? metadataNames
: [];
const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote')
const metadataNames = deepnoteMetadata?.notebook_names
const names = Array.isArray(metadataNames) && metadataNames.every(n => typeof n === 'string') ? metadataNames : []

this.selected = names.length === 0 ? null : (names[0] ?? null);
this.update();
});
this.selected = names.length === 0 ? null : (names[0] ?? null)
this.update()
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Selection broken: option value is name but code expects notebook key. Also initial selection uses a non‑schema field.

As a result, selected in notebooks fails and fromJSON never runs; the select may also have value='-' with no matching option.

Apply:

@@
-    void panel.context.ready.then(() => {
-      const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote')
-      const metadataNames = deepnoteMetadata?.notebook_names
-      const names = Array.isArray(metadataNames) && metadataNames.every(n => typeof n === 'string') ? metadataNames : []
-
-      this.selected = names.length === 0 ? null : (names[0] ?? null)
-      this.update()
-    })
+    void panel.context.ready.then(() => {
+      const meta = this.panel.context.model.getMetadata('deepnote')
+      const parsed = deepnoteMetadataSchema.safeParse(meta)
+      this.selected = parsed.success ? Object.keys(parsed.data.notebooks)[0] ?? null : null
+      this.update()
+    })
@@
-    const selected = event.target.value
-    const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote')
-    const deepnoteMetadataValidated = deepnoteMetadataSchema.safeParse(deepnoteMetadata)
+    const selectedKey = event.target.value
+    const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote')
+    const deepnoteMetadataValidated = deepnoteMetadataSchema.safeParse(deepnoteMetadata)
@@
-    const notebooks = deepnoteMetadataValidated.data.notebooks
+    const notebooks = deepnoteMetadataValidated.data.notebooks
@@
-    if (selected in notebooks) {
+    if (selectedKey in notebooks) {
       model.fromJSON({
-        cells: notebooks[selected]?.cells ?? [],
+        cells: notebooks[selectedKey]?.cells ?? [],
         metadata: {
           deepnote: {
             notebooks,
           },
         },
         nbformat: 4,
         nbformat_minor: 0,
       })
       model.dirty = false
     }
 
-    this.selected = selected
+    this.selected = selectedKey
     this.update()
   }
@@
-    const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote')
-
-    const deepnoteMetadataValidated = deepnoteMetadataSchema.safeParse(deepnoteMetadata)
-
-    const names = deepnoteMetadataValidated.success
-      ? Object.values(deepnoteMetadataValidated.data.notebooks).map(n => n.name)
-      : []
+    const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote')
+    const deepnoteMetadataValidated = deepnoteMetadataSchema.safeParse(deepnoteMetadata)
+    const entries = deepnoteMetadataValidated.success
+      ? Object.entries(deepnoteMetadataValidated.data.notebooks)
+      : []
@@
-      <HTMLSelect
-        value={this.selected ?? '-'}
+      <HTMLSelect
+        value={this.selected ?? (entries[0]?.[0] ?? '-')}
         onChange={this.handleChange}
         onKeyDown={() => {}}
         aria-label='Select active notebook'
         title='Select active notebook'
         style={{
           maxWidth: '120px',
           textOverflow: 'ellipsis',
           whiteSpace: 'nowrap',
           overflow: 'hidden',
         }}
       >
-        {names.length === 0 ? (
+        {entries.length === 0 ? (
           <option value='-'>-</option>
         ) : (
-          names.map(n => (
-            <option key={n} value={n}>
-              {n}
-            </option>
-          ))
+          entries.map(([key, n]) => (
+            <option key={key} value={key}>
+              {n.name}
+            </option>
+          ))
         )}
       </HTMLSelect>

Also applies to: 25-56, 59-92

🤖 Prompt for AI Agents
In src/components/NotebookPicker.tsx around lines 14 to 21 (and similarly 25-56,
59-92), the code reads notebook_names and sets selected to a notebook name but
elsewhere the select expects notebook keys; this causes "selected in notebooks"
checks to fail and prevents fromJSON from running. Change the initialization to
read the structured notebooks object/array from the metadata (use
metadata.notebooks or deepnote.notebooks as per schema), validate its shape, and
derive both the option display label and the option value as the notebook key
(not the name). Ensure this.selected stores the notebook key (or null) and
adjust comparisons to use keys; handle the empty/fallback case by setting
selected to '-' or null consistently and ensure the rendered <option> values
exactly match the keys so value lookups succeed.

Comment on lines 30 to 34
if (!validatedModelContent.success) {
console.error(
'Invalid .deepnote file content:',
validatedModelContent.error
);
// Return an empty notebook instead of throwing an error
model.content.cells = [];
return model;
model.content.cells = []
return model
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent error path may hide issues.

Validation failure now returns empty cells without logging. Developers/users lose visibility into malformed .deepnote files. Consider at least a warning or trace-level log.

🤖 Prompt for AI Agents
In src/deepnote-content-provider.ts around lines 30 to 34, the code returns an
empty notebook on validation failure without any logging; update this branch to
emit a warning or trace-level log that includes the validation failure details
(e.g., validatedModelContent.errors or message) and contextual info (file/id)
before setting model.content.cells = [] and returning the model so developers
can see malformed .deepnote inputs; use the module's existing logger (or
console.warn if none) and keep the log concise.

Comment on lines +48 to 50
} catch (_error) {
throw new Error('Failed to transform Deepnote YAML to notebook content.')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent error path may hide issues.

The catch block no longer logs before rethrowing. While the generic error message is preserved, the original error details are lost. Consider logging the underlying error for debugging.

🤖 Prompt for AI Agents
In src/transform-deepnote-yaml-to-notebook-content.ts around lines 48 to 50, the
catch block swallows the original error; update it to capture the caught error
(e.g., catch (error)), log the underlying error details using the module's
logger or console.error with context, and then rethrow the existing generic
Error (or wrap it while preserving the original as cause) so debug info is
retained while keeping the public message unchanged.

Comment on lines +11 to +14
cells: z.array(z.any()),
})
)
});
),
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Schema too permissive: cells accept any; strengthen to prevent invalid notebook content.

safeParse currently succeeds for arbitrary arrays, risking runtime errors when hydrating the model.

Apply:

-      cells: z.array(z.any()),
+      cells: z.array(
+        z.object({
+          cell_type: z.enum(['code', 'markdown']),
+          // Jupyter allows string or string[]
+          source: z.union([z.string(), z.array(z.string())]),
+          metadata: z.record(z.unknown()).optional(),
+          // Code cell-only fields will just be ignored for markdown
+          outputs: z.array(z.unknown()).optional(),
+          execution_count: z.number().nullable().optional(),
+        })
+      ),

@jamesbhobbs jamesbhobbs deleted the devin/GRN-4938-1760471538 branch October 21, 2025 11:20
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.

2 participants