Skip to content

feat: code mirror editors - #418

Merged
dmitriy-kostianetskiy merged 41 commits into
mainfrom
feat/codemirror
Mar 12, 2026
Merged

dmitriy-kostianetskiy merged 41 commits into
mainfrom
feat/codemirror

Conversation

@dmitriy-kostianetskiy

@dmitriy-kostianetskiy dmitriy-kostianetskiy commented Mar 5, 2026 •

Copy link
Copy Markdown
Collaborator

This PR introduces a new @sqlrooms/codemirror package and migrates several JSON editor usages from Monaco to CodeMirror, while also adding a DuckDB-focused SQL CodeMirror editor and centralizing theme/color utilities.

Changes:

  • Added @sqlrooms/codemirror (CodeMirror 6 wrappers, themes, JSON schema lint/autocomplete utilities) and an example app for editor comparison.
  • Added CodeMirror-based DuckDB SQL editor support in @sqlrooms/sql-editor (themes, keymap, schema-aware completion, hover/lint integration).
  • Centralized theme detection (useIsDarkTheme) and moved shared color utilities into @sqlrooms/utils; introduced editor color CSS variables in the UI Tailwind preset.

Summary by CodeRabbit

  • New Features

    • Added a Code Editor example app (JSON, SQL, JavaScript) and a new CodeMirror editor package with JSON/JS/SQL editors, themes, and utilities
    • Exposed DuckDB function suggestions and documentation APIs; SQL editor gains hover tooltips, richer completions, and run-query support
    • Vega editor: CodeMirror-based Vega-Lite editor with schema validation
  • Refactor

    • Migrate many editors from Monaco to CodeMirror across the repo
  • Documentation

    • New README and CHANGELOG for CodeMirror package
  • Style

    • Expanded editor CSS theme tokens and tooltip theming
  • Tests

    • Added extensive tests for JSON schema validation and autocomplete

@coderabbitai

coderabbitai Bot commented Mar 5, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new @sqlrooms/codemirror package (CodeMirror 6 integration), migrates many Monaco editor usages to CodeMirror across packages, introduces SQL/DuckDB function suggestion APIs, centralizes color/theme utilities, and adds a code-editor example app and related tests and tooling.

Changes

Cohort / File(s) Summary
CodeMirror core & features
packages/codemirror/src/components/*, packages/codemirror/src/extensions/*, packages/codemirror/src/themes/*, packages/codemirror/src/utils/*
New CodeMirrorEditor, Json/Javascript/SQL specialized editors, schema linting/autocomplete, themes, tooltip theme, utility validators, and test coverage (validate-json-schema, json-schema-autocomplete).
CodeMirror package config & docs
packages/codemirror/{package.json, tsconfig.json, jest.config.js, eslint.config.js, typedoc.js, CHANGELOG.md, README.md}
New package metadata, build/test/lint configs, README and changelog for @sqlrooms/codemirror.
SQL editor (CodeMirror) & SQL features
packages/sql-editor/src/{SqlCodeMirrorEditor.tsx, codemirror/extensions/*, codemirror/themes/*, components/*}
New SqlCodeMirrorEditor, SQL dialect/duckdb extensions, completion/hover/tooltip, schema conversion utilities, FunctionDocumentation and table/column tooltip components.
Editor migrations
packages/data-table/src/useArrowDataTable.tsx, packages/room-shell/src/RoomShellCommandPalette.tsx, packages/cells/src/components/*, packages/vega/src/*, packages/sql-editor/src/components/*, examples/*/code-editor/*
Replaced Monaco editors with CodeMirror equivalents across multiple packages and examples; updated options, event hooks (onRunQuery), and removed Monaco-specific config.
DuckDB function APIs
packages/duckdb-core/src/{duckdb-function-utils.ts, get-function-suggestions.ts, get-function-documentation.ts}, packages/duckdb-core/package.json
New utilities/types for FunctionSuggestion and GroupedFunctionSuggestion, plus memoized getFunctionSuggestions and getFunctionDocumentation querying duckdb_functions().
Utilities & shared helpers
packages/utils/src/{color.ts, renderComponentToString.tsx}, packages/utils/src/index.ts, packages/utils/__tests__/color.test.ts
Added hslToHex, getCssColor, getMonospaceFont, renderComponentToString, and tests; re-exported these utilities for other packages.
Monaco package updates
packages/monaco-editor/src/{components/MonacoEditor.tsx, utils/color-utils.ts, index.ts}, packages/monaco-editor/package.json
Removed local color utils in favor of @sqlrooms/utils, updated theme handling and MonacoEditor theme prop type, removed some public color exports.
Examples & configs
examples/code-editor/*, examples/*/vite/tsconfig/*, packages/*/eslint.config.js, packages/*/tsconfig.*.json
New code-editor example app, ESLint/Vite/TS configs for examples, small formatting/TS config additions.
Minor formatting / lint changes
assorted packages (packages/*, examples/*)
Whitespace/class ordering tweaks, ESLint rule change (@typescript-eslint/no-unused-vars set to warn), dependency swaps (monaco → codemirror in several package.jsons), minor test/mock updates.

Sequence Diagram(s)

sequenceDiagram
  participant Editor as CodeMirrorEditor
  participant Completion as CompletionExtension
  participant Connector as DuckDbConnector
  participant DB as DuckDB
  participant Renderer as FunctionDocumentationRenderer

  Editor->>Completion: user types function name / triggers completion
  Completion->>Connector: request getFunctionSuggestions(query)
  Connector->>DB: run duckdb_functions() query
  DB-->>Connector: returns rows
  Connector-->>Completion: grouped function suggestions
  Completion->>Renderer: render documentation (renderComponentToString)
  Renderer-->>Editor: attach completion items + hover tooltip
Loading

(Note: colored rectangles not used in this simple flow.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ilyabo

🐰 I hopped through tokens, lines, and themes,
Swapped Monaco for CodeMirror in daring dreams.
Autocomplete sings and schemas take flight,
DuckDB whispers functions in soft candlelight.
Hop onward, dear devs—our editors gleam bright! 🥕✨

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/codemirror

Copilot AI 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.

Pull request overview

This PR introduces a new @sqlrooms/codemirror package and migrates several JSON editor usages from Monaco to CodeMirror, while also adding a DuckDB-focused SQL CodeMirror editor and centralizing theme/color utilities.

Changes:

  • Added @sqlrooms/codemirror (CodeMirror 6 wrappers, themes, JSON schema lint/autocomplete utilities) and an example app for editor comparison.
  • Added CodeMirror-based DuckDB SQL editor support in @sqlrooms/sql-editor (themes, keymap, schema-aware completion, hover/lint integration).
  • Centralized theme detection (useIsDarkTheme) and moved shared color utilities into @sqlrooms/utils; introduced editor color CSS variables in the UI Tailwind preset.

Reviewed changes

Copilot reviewed 87 out of 89 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/utils/src/index.ts Re-exports new shared color/theme helpers from utils.
packages/utils/src/color.ts Adds HSL/CSS-variable color + monospace font helpers.
packages/ui/tailwind-preset.css Adds editor-related CSS variables and theme mappings.
packages/ui/src/theme/theme-provider.tsx Exports Theme type for cross-package consumption.
packages/ui/src/index.ts Re-exports Theme and new useIsDarkTheme hook.
packages/ui/src/hooks/useIsSystemPrefersDark.ts Adds system dark-mode detection hook.
packages/ui/src/hooks/useIsDarkTheme.ts Adds centralized “is dark?” logic for app/system/override.
packages/ui/src/hooks/use-mobile.ts Formatting-only adjustments.
packages/ui/src/components/sidebar.tsx Formatting-only adjustments.
packages/sql-editor/src/index.ts Exposes new CodeMirror editor + types from sql-editor.
packages/sql-editor/src/components/QueryEditorPanelEditor.tsx Moves Cmd/Ctrl+Enter handling into SqlMonacoEditor via onRunQuery.
packages/sql-editor/src/components/DuckdbCodeMirrorEditor.tsx Adds DuckDB-oriented SQL CodeMirror editor component.
packages/sql-editor/src/codemirror/utils/schema-converter.ts Converts SQLRooms schemas to CodeMirror SQL namespace format.
packages/sql-editor/src/codemirror/utils/keyword-docs.ts Lazy-loads keyword docs for tooltips/completions.
packages/sql-editor/src/codemirror/themes/sql-theme.ts Adds SQL highlighting theme using editor CSS variables.
packages/sql-editor/src/codemirror/extensions/sql-keymap.ts Adds Mod-Enter keybinding to run SQL.
packages/sql-editor/src/codemirror/extensions/duckdb-sql.ts Adds DuckDB dialect language support + keyword tooltip info.
packages/sql-editor/src/codemirror/extensions/duckdb-sql-extension.ts Adds marimo SQL lint/hover/gutter integration.
packages/sql-editor/src/codemirror/extensions/duckdb-completion.ts Adds custom + DuckDB dynamic function completions.
packages/sql-editor/src/codemirror/extensions/duck-db.ts Composes DuckDB SQL + completion + lint/hover extensions.
packages/sql-editor/src/SqlMonacoEditor.tsx Adds onRunQuery API and migrates function suggestions import.
packages/sql-editor/package.json Adds CodeMirror + marimo SQL dependencies.
packages/room-store/src/CommandSlice.ts Formatting-only adjustments.
packages/room-store/tests/CommandSlice.test.ts Formatting-only adjustments.
packages/room-shell/src/RoomShellCommandPalette.tsx Migrates JSON input editor from Monaco to CodeMirror.
packages/room-shell/package.json Swaps Monaco editor dependency for CodeMirror.
packages/preset-eslint/base.js Tightens unused-vars to warn.
packages/notebook/src/cells/CellContainer.tsx Tailwind class ordering/cleanup.
packages/notebook/src/cellOperations/AddNewCellTabs.tsx Tailwind class ordering/cleanup.
packages/monaco-editor/src/utils/color-utils.ts Uses shared utils color helpers for Monaco theming.
packages/monaco-editor/src/index.ts Stops exporting color utils from monaco-editor package root.
packages/monaco-editor/src/components/MonacoEditor.tsx Uses useIsDarkTheme + Theme type; updates defaults.
packages/monaco-editor/package.json Adds @sqlrooms/utils dependency; adjusts monaco version.
packages/monaco-editor/README.md Updates theme prop docs to new Theme type.
packages/duckdb/src/index.ts Re-exports getFunctionSuggestions.
packages/duckdb-core/src/index.ts Re-exports getFunctionSuggestions from core implementation.
packages/duckdb-core/src/duckdb-function-suggestions.ts Refactors imports to duckdb-core internals + memoizeOnce.
packages/duckdb-core/package.json Adds @sqlrooms/utils dependency.
packages/data-table/src/useArrowDataTable.tsx Migrates JSON viewer from Monaco to CodeMirror.
packages/data-table/package.json Swaps Monaco editor dependency for CodeMirror.
packages/codemirror/typedoc.js Adds typedoc config for new package.
packages/codemirror/tsconfig.json Adds TS config for new package build output.
packages/codemirror/src/utils/diagnostics-utils.ts Adds diagnostics extraction utility for CodeMirror lint.
packages/codemirror/src/themes/sqlrooms-theme.ts Adds general-purpose SQLRooms syntax theme for CodeMirror.
packages/codemirror/src/themes/json-theme.ts Adds JSON syntax theme using editor CSS variables.
packages/codemirror/src/themes/highlight-active-line-theme.ts Adds active line theme helper.
packages/codemirror/src/themes/base-theme.ts Adds base CodeMirror theme wired to SQLRooms CSS variables/fonts.
packages/codemirror/src/index.ts Exports CodeMirror components/types + base theme utilities.
packages/codemirror/src/extensions/json-schema-lint.ts Adds AJV-based JSON schema linting integration.
packages/codemirror/src/extensions/json-schema-autocomplete.ts Adds JSON schema-driven autocomplete for keys/values.
packages/codemirror/src/extensions/auto-trigger.ts Adds completion auto-trigger behavior on quote insertion.
packages/codemirror/src/components/JsonCodeMirrorEditor.tsx Adds JSON editor wrapper with schema linting/autocomplete.
packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx Adds JavaScript/JSX editor wrapper with SQLRooms theme.
packages/codemirror/src/components/CodeMirrorEditor.tsx Adds base CodeMirror editor wrapper with options + validation callback.
packages/codemirror/package.json Defines new package dependencies and scripts.
packages/codemirror/eslint.config.js Adds eslint config for new package.
packages/codemirror/README.md Adds comprehensive documentation for codemirror package.
packages/codemirror/CHANGELOG.md Adds initial changelog for codemirror package.
packages/cells/typedoc.js Formatting-only adjustment.
packages/cells/src/components/VegaCellContent.tsx Tailwind class ordering/cleanup.
packages/cells/src/components/TextCellContent.tsx Tailwind class ordering/cleanup.
packages/cells/src/components/InputCellContent.tsx Tailwind class ordering/cleanup.
packages/cells/eslint.config.js Formatting-only adjustment.
packages/canvas/src/nodes/CanvasNodeContainer.tsx Tailwind class ordering/cleanup.
packages/ai-core/src/components/tools/ToolErrorMessage.tsx Migrates JSON details viewer from Monaco to CodeMirror.
packages/ai-core/src/components/ToolPartRenderer.tsx Tailwind class ordering/cleanup.
packages/ai-core/src/components/PromptSuggestions.tsx Adjusts sizing/layout classes for prompt suggestions UI.
packages/ai-core/src/components/AnalysisResult.tsx Adds prompt wrapping + copy button for prompt display.
packages/ai-core/package.json Swaps Monaco editor dependency for CodeMirror.
examples/notebook/src/NotebookPanel.tsx Tailwind class ordering/cleanup.
examples/multi-room/src/RoomPage.tsx Formatting-only adjustments.
examples/code-editor/vite.config.ts Adds new Vite example config for editor comparison app.
examples/code-editor/tsconfig.node.json Adds Node TS config for Vite example.
examples/code-editor/tsconfig.json Adds TS project references for Vite example.
examples/code-editor/tsconfig.app.json Adds app TS config for Vite example.
examples/code-editor/src/vite-env.d.ts Adds Vite env typings.
examples/code-editor/src/main.tsx Adds Vite example entry point.
examples/code-editor/src/index.css Adds Tailwind + SQLRooms preset wiring for example app.
examples/code-editor/src/SqlEditor.tsx Adds SQL editor switcher (Monaco vs CodeMirror).
examples/code-editor/src/LanguageSwitch.tsx Adds language selector UI.
examples/code-editor/src/JsonEditor.tsx Adds JSON editor switcher (Monaco vs CodeMirror).
examples/code-editor/src/JavascriptEditor.tsx Adds JS editor switcher (Monaco vs CodeMirror).
examples/code-editor/src/EditorTypeSwitch.tsx Adds editor type selector UI.
examples/code-editor/src/App.tsx Adds full example app demonstrating editors + theme switching.
examples/code-editor/package.json Adds example app dependencies/scripts.
examples/code-editor/index.html Adds example app HTML entry.
examples/code-editor/eslint.config.js Adds eslint config for example app.
examples/code-editor/README.md Adds minimal readme for new example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread packages/ai-core/src/components/PromptSuggestions.tsx
Comment thread packages/sql-editor/src/codemirror/utils/keyword-docs.ts Outdated
Comment thread packages/utils/src/color.ts
Comment thread packages/codemirror/src/components/CodeMirrorEditor.tsx
Comment thread packages/sql-editor/src/codemirror/extensions/duckdb-sql.ts Outdated
Comment thread packages/codemirror/src/extensions/json-schema-lint.ts Outdated
Comment thread packages/codemirror/src/extensions/json-schema-autocomplete.ts Outdated
Comment thread packages/codemirror/src/components/CodeMirrorEditor.tsx
Comment thread packages/ai-core/src/components/PromptSuggestions.tsx
@dmitriy-kostianetskiy dmitriy-kostianetskiy self-assigned this Mar 5, 2026
@dmitriy-kostianetskiy
dmitriy-kostianetskiy marked this pull request as ready for review March 5, 2026 16:51
@dmitriy-kostianetskiy dmitriy-kostianetskiy changed the title feat: code mirror feat: code mirror editors Mar 5, 2026
@coderabbitai

coderabbitai Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/sqlrooms/sqlrooms/issues/comments/4004667780","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: skip review by coderabbit.ai -->\n\n> [!IMPORTANT]\n> ## Review skipped\n> \n> Draft detected.\n> \n> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: defaults\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `050eed4c-f74a-4810-8e04-8a91d353b370`\n> \n> </details>\n> \n> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.\n> \n> Use the checkbox below for a quick retry:\n> - [ ] <!-- {\"checkboxId\": \"e9bb8d72-00e8-4f67-9cb2-caf3b22574fe\"} --> 🔍 Trigger review\n\n<!-- end of auto-generated comment: skip review by coderabbit.ai -->\n\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `feat/codemirror`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=sqlrooms/sqlrooms&utm_content=418)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}}}

@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: 15

🧹 Nitpick comments (11)
packages/ai-core/src/components/PromptSuggestions.tsx (1)

65-65: Remove conflicting height utilities (h-18 vs max-h-14 min-h-14).

h-18 is contradicted by max-h-14 min-h-14, resulting in a fixed height of 14 rather than 18. Additionally, w-48 max-w-48 min-w-48 is redundant—all three specify the same value. Clean this up to h-14 w-48 in both locations.

Proposed cleanup
- 'h-18 max-h-14 min-h-14 w-48 max-w-48 min-w-48',
+ 'h-14 w-48',

Also applies to: 144-144

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

In `@packages/ai-core/src/components/PromptSuggestions.tsx` at line 65, In
PromptSuggestions.tsx update the conflicting Tailwind utility strings (currently
containing 'h-18 max-h-14 min-h-14 w-48 max-w-48 min-w-48') to the cleaned,
non-conflicting utilities 'h-14 w-48' in both places where these classes appear
(the className used in the PromptSuggestions component around the occurrences
noted by the reviewer), ensuring you replace both occurrences (the one near the
first block and the other at the later occurrence) so height and width are
explicit and not redundant.
packages/sql-editor/src/codemirror/utils/schema-converter.ts (1)

8-10: Avoid namespace key collisions for same table names.

Using only table.table.table can overwrite entries when identical table names exist across schemas/catalogs.

Suggested patch
 for (const table of tables) {
-  namespace[table.table.table] = table.columns.map((col) => col.name);
+  const {catalog, schema, table: tableName} = table.table;
+  const qualifiedName = [catalog, schema, tableName].filter(Boolean).join('.');
+  namespace[qualifiedName] = table.columns.map((col) => col.name);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/codemirror/utils/schema-converter.ts` around lines 8
- 10, The current loop assigns namespace[table.table.table] which can collide
for identical table names across schemas/catalogs; change the key to a
fully-qualified identifier using available metadata (e.g., include
table.table.schema and/or table.table.catalog) so each entry is unique (for
example build a key from table.table.catalog + '.' + table.table.schema + '.' +
table.table.table) while keeping the value as table.columns.map(col =>
col.name); update any consumers that expect the old key format if needed.
packages/sql-editor/src/codemirror/utils/keyword-docs.ts (1)

6-17: Consider memoizing the merged result.

While dynamic imports are cached by the module system, the merge operation runs on every call. If getKeywordDocs is invoked frequently (e.g., on each keystroke for hover tooltips), caching the merged result would avoid redundant object spreads.

♻️ Proposed memoization
+let cachedDocs: Record<string, KeywordDoc> | null = null;
+
 /** Lazily loads DuckDB keyword documentation from marimo-sql data files */
 export const getKeywordDocs = async (): Promise<Record<string, KeywordDoc>> => {
+  if (cachedDocs) {
+    return cachedDocs;
+  }
+
   const [commonKeywords, duckdbKeywords] = await Promise.all([
     import('@marimo-team/codemirror-sql/data/common-keywords.json'),
     import('@marimo-team/codemirror-sql/data/duckdb-keywords.json'),
   ]);

-  return {
+  cachedDocs = {
     ...commonKeywords.default.keywords,
     ...duckdbKeywords.default.keywords,
   };
+  return cachedDocs;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/codemirror/utils/keyword-docs.ts` around lines 6 -
17, getKeywordDocs currently re-merges commonKeywords.default.keywords and
duckdbKeywords.default.keywords on every call; add a module-level cache (e.g.,
let cachedKeywordDocs: Promise<Record<string, KeywordDoc>> | null) and return it
if present to avoid repeated spreads. In getKeywordDocs, if cachedKeywordDocs is
null assign it to the Promise that imports both JSONs and returns the merged
object (using commonKeywords and duckdbKeywords), otherwise return the cached
promise; this also ensures concurrent callers share the same in-flight load. Use
the existing symbols getKeywordDocs, commonKeywords, and duckdbKeywords to
locate where to add the cache.
examples/code-editor/src/SqlEditor.tsx (1)

42-42: Redundant arrow function wrapper.

The onChange callback wraps the value unnecessarily since value is already a string.

♻️ Proposed simplification
       onChange={(value: string) => onChange(value)}
+      onChange={onChange}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/code-editor/src/SqlEditor.tsx` at line 42, The onChange prop in
SqlEditor.tsx currently wraps the incoming string in a redundant arrow
(onChange={(value: string) => onChange(value)}); replace this with a direct
reference to the handler to remove the unnecessary wrapper — use the existing
onChange prop directly (onChange={onChange}) so the component forwards the
string value without creating an extra function; update the prop usage where the
SqlEditor component renders the input/CodeEditor to reference the onChange
handler directly.
packages/codemirror/src/extensions/auto-trigger.ts (1)

20-26: Consider early exit once a quote is found.

The iterChanges callback continues iterating through all changes even after insertedQuote is set to true. While this is functionally correct, it's unnecessary work.

♻️ Proposed optimization

Unfortunately, iterChanges doesn't support breaking out early. If performance becomes a concern with large change sets, you could track the flag outside and check it, though the current implementation is fine for typical use cases.

     update.changes.iterChanges((_fromA, _toA, _fromB, _toB, inserted) => {
+      if (insertedQuote) return; // Skip remaining iterations
       const text = inserted.toString();
       // Check if the inserted text is a quote (could be single or double)
       if (/^["']$/.test(text)) {
         insertedQuote = true;
       }
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/extensions/auto-trigger.ts` around lines 20 - 26, The
iterChanges callback currently continues processing even after a quote is found;
modify update.changes.iterChanges in auto-trigger.ts to short-circuit the
callback by checking the outer boolean flag (insertedQuote) at the start of the
callback and returning early if it's already true, so once insertedQuote is set
by the /^["']$/ test no further change bodies are processed; ensure the outer
variable (insertedQuote) is declared/initialized before calling
update.changes.iterChanges and used inside the callback to skip work.
packages/sql-editor/src/components/QueryEditorPanelEditor.tsx (1)

19-21: Prefer DuckDB-specific custom selectors over direct s.db.* access.

Line 19 and Line 21 currently read DuckDB state directly from store shape. Please switch this to the package’s custom DuckDB selector hooks to keep state access decoupled and consistent.

As per coding guidelines "Use custom Zustand selectors for accessing DuckDB store state (e.g., useSql(), useDuckDb(), useDuckDbQuery()) rather than direct connector access".

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

In `@packages/sql-editor/src/components/QueryEditorPanelEditor.tsx` around lines
19 - 21, The code is directly accessing DuckDB state via useStoreWithSqlEditor
((s) => s.db.tables and (s) => s.db.connector); replace those direct accesses
with the package's DuckDB-specific selectors (e.g., useDuckDb(), useSql(), or
useDuckDbQuery()) so state access is decoupled. Specifically, change the
tableSchemas selector to use the DuckDB table/schema selector (e.g., useDuckDb()
or useDuckDbQuery() to get tables) and replace connector usage with the DuckDB
connector selector (e.g., useDuckDb().connector or the dedicated connector
selector); leave runQuery (parseAndRunQuery) wired to the sql editor selector or
migrate it to useSql() if available. Ensure you import the appropriate hook(s)
and remove direct (s).db.* references in QueryEditorPanelEditor.tsx.
packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx (1)

19-35: Avoid defaulting userExtensions to a new array in destructuring.

This creates a fresh array every render when the prop is omitted, so the useMemo cache is invalidated unnecessarily.

♻️ Suggested update
-  extensions: userExtensions = [],
+  extensions: userExtensions,
@@
-      ...userExtensions,
+      ...(userExtensions ?? []),
@@
-  }, [userExtensions, isDark]);
+  }, [userExtensions, isDark]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx` around
lines 19 - 35, The default parameter sets userExtensions = [] causing a new
array each render and busting the useMemo for extensions; remove the default in
the function signature (stop defaulting userExtensions to a new array) and
instead handle a possibly undefined value inside the useMemo (e.g., const
extensions = useMemo(() => [ javascript({jsx:true}),
createSqlroomsTheme({isDark}), ...(userExtensions ?? []) ], [userExtensions,
isDark]) ) so the dependency check and memoization work correctly while still
supporting omitted props in the JavascriptCodeMirrorEditor component.
examples/code-editor/src/EditorTypeSwitch.tsx (1)

15-31: Add pressed-state semantics for the editor toggle buttons.

The selected state is currently visual-only. Adding aria-pressed improves accessibility for screen readers.

♿ Suggested update
-    <div className="border-border bg-muted flex gap-1 rounded-md border p-1">
+    <div
+      className="border-border bg-muted flex gap-1 rounded-md border p-1"
+      role="group"
+      aria-label="Editor type"
+    >
       <Button
         onClick={() => onChange('monaco')}
+        aria-pressed={value === 'monaco'}
         variant={value === 'monaco' ? 'default' : 'ghost'}
         size="sm"
         className="h-8 px-3"
       >
         Monaco
       </Button>
       <Button
         onClick={() => onChange('codemirror')}
+        aria-pressed={value === 'codemirror'}
         variant={value === 'codemirror' ? 'default' : 'ghost'}
         size="sm"
         className="h-8 px-3"
       >
         CodeMirror
       </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/code-editor/src/EditorTypeSwitch.tsx` around lines 15 - 31, The
toggle buttons in EditorTypeSwitch (the two Button elements that call
onChange('monaco') and onChange('codemirror')) only convey selection visually;
add accessible pressed-state semantics by setting aria-pressed on each Button to
a boolean that reflects whether value === 'monaco' (for the Monaco button) and
value === 'codemirror' (for the CodeMirror button), so screen readers get the
current toggle state while preserving the existing onClick/variant behavior.
packages/codemirror/src/components/JsonCodeMirrorEditor.tsx (1)

29-57: Use a nullable userExtensions prop instead of = [] to preserve memoization.

Like the JS editor, defaulting to [] here allocates a new array each render and defeats useMemo stability when no extensions are passed.

♻️ Suggested update
-  extensions: userExtensions = [],
+  extensions: userExtensions,
@@
-      ...userExtensions,
+      ...(userExtensions ?? []),
@@
-  }, [schema, userExtensions, isDark, hideGutter]);
+  }, [schema, userExtensions, isDark, hideGutter]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/components/JsonCodeMirrorEditor.tsx` around lines 29
- 57, The component currently sets a default parameter userExtensions = [] which
creates a new array each render and breaks useMemo stability for the extensions
variable; remove the "= []" default from the function signature so
userExtensions can be nullable/undefined, then inside the useMemo (where
extensions is built) use a stable expression like ...(userExtensions ?? []) to
spread extensions only when provided, ensuring useMemo can properly memoize
based on the userExtensions reference; update any TypeScript prop typing for
userExtensions to allow undefined/null if necessary.
packages/codemirror/src/components/CodeMirrorEditor.tsx (2)

198-209: Extensions effect runs unnecessarily on initial mount.

The initialization effect (lines 156-180) creates the editor with extensions, and this effect also runs on mount due to extensions being in its dependency array. This causes an immediate reconfiguration with the same extensions.

Consider skipping the reconfigure on initial mount:

♻️ Proposed optimization
+  const isInitialMount = useRef(true);
+
   // Handle extensions updates
   useEffect(() => {
     const view = viewRef.current;

     if (!view) {
       return;
     }

+    // Skip reconfigure on initial mount since editor was just created with these extensions
+    if (isInitialMount.current) {
+      isInitialMount.current = false;
+      return;
+    }
+
     view.dispatch({
       effects: StateEffect.reconfigure.of(extensions),
     });
   }, [extensions]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/components/CodeMirrorEditor.tsx` around lines 198 -
209, The reconfigure effect (useEffect watching extensions that calls
view.dispatch(StateEffect.reconfigure.of(extensions))) runs on initial mount and
redundantly reapplies the same extensions; add a guard flag (e.g., an
initializedRef boolean) to skip the first run: set initializedRef.current = true
when the editor is created in the initialization effect, and in the extensions
effect only call view.dispatch(StateEffect.reconfigure.of(extensions)) if
initializedRef.current is true (and viewRef.current exists), thereby avoiding
the unnecessary reconfigure on mount.

151-152: Document that extensions prop should be memoized.

The userExtensions array is a dependency of this useMemo. If the parent component passes a non-memoized array (e.g., extensions={[ext1, ext2]}), a new reference is created on every render, causing unnecessary editor reconfigurations.

Consider adding a JSDoc note on the extensions prop recommending memoization, or wrap with a ref-based comparison to detect actual changes.

📝 Suggested documentation update
 export interface CodeMirrorEditorProps {
   // ...
-  /** Additional CodeMirror extensions to apply */
+  /** Additional CodeMirror extensions to apply. Should be memoized to prevent unnecessary reconfigurations. */
   extensions?: Extension[];
   // ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/components/CodeMirrorEditor.tsx` around lines 151 -
152, The extensions prop (userExtensions) passed into the useMemo that builds
the editor config (in CodeMirrorEditor) must be documented as needing
memoization or stabilized; update the JSDoc for the extensions prop to recommend
callers memoize (e.g., useMemo) and, to harden against un-memoized callers, wrap
the existing useMemo dependency (userExtensions) with a cheap ref-based equality
check inside CodeMirrorEditor (useRef + shallow/deep comparison) so the memo
only invalidates when actual extension identities change; reference the
userExtensions variable and the useMemo that includes options.autocompletion and
userExtensions when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/code-editor/package.json`:
- Line 32: Update the TypeScript dependency in package.json to match the
workspace standard by changing the "typescript" entry currently set to "5.9.2"
to "5.9.3"; locate the "typescript" dependency line in examples/code-editor's
package.json and update the version string so the project uses the repo-standard
TypeScript version.

In `@examples/code-editor/src/index.css`:
- Around line 5-9: Stylelint is flagging Tailwind v4's `@source` directive as an
unknown at-rule; update the stylelint config (the .stylelintrc.json) to allow
Tailwind directives by adding an at-rule-no-unknown rule that ignores "source"
(and also "tailwind", "apply", "layer") so files using `@source` (e.g., the
directives in examples/code-editor/src/index.css) no longer fail CI; modify
.stylelintrc.json to include "rules": { "at-rule-no-unknown": [true, {
"ignoreAtRules": ["source","tailwind","apply","layer"] }] }.

In `@examples/code-editor/src/JsonEditor.tsx`:
- Line 13: The component signature uses React.FC<JsonEditorProps> but React
isn’t imported, causing TS errors; fix by importing the React types and/or FC
from 'react' and updating the top of the file accordingly (e.g., add an import
for React or import type { FC } and use FC<JsonEditorProps>), ensuring the
JsonEditor and JsonEditorProps references resolve for the TypeScript compiler.

In `@examples/code-editor/src/SqlEditor.tsx`:
- Around line 1-4: The file uses the React.FC type (React.FC) but does not
import React; add an explicit React import at the top of SqlEditor.tsx (e.g.,
import React from 'react') so the React symbol is defined, or alternatively
remove the React. prefix and import the FC type (import type { FC } from
'react') and update the component signature accordingly; locate usages of
React.FC in this file (the SqlEditor component declaration) and apply one of
these two fixes to keep imports consistent with the component signature.

In `@examples/code-editor/vite.config.ts`:
- Around line 6-8: Introduce a Zod schema named ConfigSchema that matches the
shape passed to defineConfig (plugins: array etc.), parse the config object
using ConfigSchema.parse(...) and infer the exported type with z.infer<typeof
ConfigSchema>, then pass the parsed/typed config into defineConfig instead of
the raw object; update the export to use the parsedConfig variable and ensure
imports include z from "zod" and any needed types so the final export is export
default defineConfig(parsedConfig) with type safety via z.infer.

In `@packages/cells/src/components/InputCellContent.tsx`:
- Line 106: The EditableText instance in InputCellContent has classes "ring-0"
and "outline-none" which remove visible focus; update the component's className
to preserve an accessible focus indicator by replacing or augmenting those
styles with a focus-visible utility (e.g., add a "focus-visible:ring-2
focus-visible:ring-blue-500 focus-visible:outline-none" or equivalent) so
keyboard users see a clear focus when not actively editing; locate the
EditableText usage in InputCellContent (className on the EditableText element)
and add the focus-visible classes while keeping existing editing-focused styles
such as focus:border-blue-500 focus:ring-blue-500 intact.

In `@packages/codemirror/CHANGELOG.md`:
- Line 10: Changelog entry wording is redundant: update the line mentioning the
CodeMirrorEditor component to replace "SQL language support" with "SQL support"
so it reads "CodeMirrorEditor component with JavaScript, JSON, and SQL support";
locate the entry referencing `CodeMirrorEditor` in CHANGELOG.md and make this
phrasing change.

In `@packages/codemirror/README.md`:
- Around line 204-235: The example's MyJsonEditor initializes json state as an
object but JsonCodeMirrorEditor.onChange supplies a string (raw JSON), causing a
type mismatch; fix by making the state type and handler agree: either initialize
useState with a JSON string (e.g., JSON.stringify({...})) and keep
onChange={setJson} or keep object state and change onChange to parse the
incoming string and call setJson(JSON.parse(value)) (also handle parse errors) —
update references in MyJsonEditor, the useState call, and the
JsonCodeMirrorEditor onChange handler accordingly.

In `@packages/codemirror/src/extensions/json-schema-autocomplete.ts`:
- Around line 43-51: The computed JSON path returned by getJsonPath sometimes
already contains the current property key, so when extractValueCompletions (and
the later propertySchema lookup) resolves that key again it ends up at the wrong
schema level and returns null; fix this by normalizing the path before resolving
the property schema: when propertyName is present, strip the trailing property
segment from the path (or otherwise ensure you pass the parent path) before
calling extractValueCompletions / resolving propertySchema in the logic around
getJsonPath, getPropertyName and extractValueCompletions so the schema lookup
uses the parent object schema rather than the property key itself.

In `@packages/codemirror/src/utils/validate-json-schema.ts`:
- Around line 99-108: The issue: path segments from error.instancePath are left
as strings so findNodeAtLocation fails for array indices; fix by converting
numeric-looking segments to numbers before calling findNodeAtLocation. Update
the pathSegments creation (derived from error.instancePath) to map each segment:
if /^[0-9]+$/ then convert to Number(segment) else keep as string, then pass
that array into findNodeAtLocation(tree, pathSegments) so array items resolve
correctly; check symbols pathSegments, error.instancePath, findNodeAtLocation,
Node and tree when making the change.
- Around line 21-32: The current implementation recreates Ajv and recompiles the
schema on every lint pass (inside the hot path where `ajv`, `addFormats(ajv)`,
and `validate = ajv.compile(schema)` are executed), causing performance issues;
refactor by moving Ajv initialization and format registration out of the
per-change path and implement a cache (e.g., a Map or WeakMap) keyed by schema
identity or a stable schema hash to store compiled validators, so lookup returns
the cached `validate` function when the same schema is used and only compiles
once per unique schema.

In `@packages/data-table/src/useArrowDataTable.tsx`:
- Around line 199-200: Remove the unnecessary cast: pass parsedJson directly to
the JsonCodeMirrorEditor value prop instead of using "parsedJson as any". In the
useArrowDataTable file update the usage in the JsonCodeMirrorEditor component
(symbol: JsonCodeMirrorEditor and variable: parsedJson) so the prop is
value={parsedJson}; if TypeScript complains, tighten the parsedJson
declaration/return type where it’s produced (the JSON parsing logic in
useArrowDataTable) to be string | object to match the component's value?: string
| object signature.

In `@packages/sql-editor/src/codemirror/extensions/duckdb-completion.ts`:
- Around line 67-83: Add cancellation support by changing getFunctionSuggestions
to accept an optional signal?: AbortSignal and threading that signal into any
connector.query() calls; update the implementation in
duckdb-function-suggestions.ts (e.g., getFunctionSuggestionsImpl /
getFunctionSuggestions) to accept signal and pass { signal } to
connector.query(), then update callers in duckdb-completion.ts (where
getFunctionSuggestions is invoked) and SqlMonacoEditor.tsx to create an
AbortController per completion request and pass controller.signal, aborting
previous controllers on new requests so stale async lookups are cancelled.

In `@packages/sql-editor/src/components/DuckdbCodeMirrorEditor.tsx`:
- Around line 59-64: The memoization of currentSchemas using useMemo causes
getLatestSchemas().tableSchemas to become stale; change currentSchemas to call
getLatestSchemas() directly each render (or fall back to tableSchemas) instead
of caching its result—replace the useMemo block that defines currentSchemas with
a simple conditional that invokes getLatestSchemas() when present (referencing
getLatestSchemas and tableSchemas and the currentSchemas variable) so the
component always reads the latest schemas.

In `@packages/sql-editor/src/SqlMonacoEditor.tsx`:
- Around line 339-360: Shortcut binding for Ctrl/Cmd+Enter in SqlMonacoEditor is
registered only once using editor.onKeyDown and can become stale if onRunQuery
changes; replace the one-time bind with a controlled effect: either store the
latest onRunQuery in a ref and call that ref from a single persistent keydown
listener, or register the listener inside a useEffect that depends on onRunQuery
and disposes the previous listener on cleanup. Ensure the listener created via
editor.onKeyDown still calls editor.getModel(), editor.getSelection(),
editor.getValue(), and computes selectedValue/isSelectionEmpty as before, but
that it always invokes the current onRunQuery and properly disposes the
disposable to avoid duplicates or leaks.

---

Nitpick comments:
In `@examples/code-editor/src/EditorTypeSwitch.tsx`:
- Around line 15-31: The toggle buttons in EditorTypeSwitch (the two Button
elements that call onChange('monaco') and onChange('codemirror')) only convey
selection visually; add accessible pressed-state semantics by setting
aria-pressed on each Button to a boolean that reflects whether value ===
'monaco' (for the Monaco button) and value === 'codemirror' (for the CodeMirror
button), so screen readers get the current toggle state while preserving the
existing onClick/variant behavior.

In `@examples/code-editor/src/SqlEditor.tsx`:
- Line 42: The onChange prop in SqlEditor.tsx currently wraps the incoming
string in a redundant arrow (onChange={(value: string) => onChange(value)});
replace this with a direct reference to the handler to remove the unnecessary
wrapper — use the existing onChange prop directly (onChange={onChange}) so the
component forwards the string value without creating an extra function; update
the prop usage where the SqlEditor component renders the input/CodeEditor to
reference the onChange handler directly.

In `@packages/ai-core/src/components/PromptSuggestions.tsx`:
- Line 65: In PromptSuggestions.tsx update the conflicting Tailwind utility
strings (currently containing 'h-18 max-h-14 min-h-14 w-48 max-w-48 min-w-48')
to the cleaned, non-conflicting utilities 'h-14 w-48' in both places where these
classes appear (the className used in the PromptSuggestions component around the
occurrences noted by the reviewer), ensuring you replace both occurrences (the
one near the first block and the other at the later occurrence) so height and
width are explicit and not redundant.

In `@packages/codemirror/src/components/CodeMirrorEditor.tsx`:
- Around line 198-209: The reconfigure effect (useEffect watching extensions
that calls view.dispatch(StateEffect.reconfigure.of(extensions))) runs on
initial mount and redundantly reapplies the same extensions; add a guard flag
(e.g., an initializedRef boolean) to skip the first run: set
initializedRef.current = true when the editor is created in the initialization
effect, and in the extensions effect only call
view.dispatch(StateEffect.reconfigure.of(extensions)) if initializedRef.current
is true (and viewRef.current exists), thereby avoiding the unnecessary
reconfigure on mount.
- Around line 151-152: The extensions prop (userExtensions) passed into the
useMemo that builds the editor config (in CodeMirrorEditor) must be documented
as needing memoization or stabilized; update the JSDoc for the extensions prop
to recommend callers memoize (e.g., useMemo) and, to harden against un-memoized
callers, wrap the existing useMemo dependency (userExtensions) with a cheap
ref-based equality check inside CodeMirrorEditor (useRef + shallow/deep
comparison) so the memo only invalidates when actual extension identities
change; reference the userExtensions variable and the useMemo that includes
options.autocompletion and userExtensions when making the change.

In `@packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx`:
- Around line 19-35: The default parameter sets userExtensions = [] causing a
new array each render and busting the useMemo for extensions; remove the default
in the function signature (stop defaulting userExtensions to a new array) and
instead handle a possibly undefined value inside the useMemo (e.g., const
extensions = useMemo(() => [ javascript({jsx:true}),
createSqlroomsTheme({isDark}), ...(userExtensions ?? []) ], [userExtensions,
isDark]) ) so the dependency check and memoization work correctly while still
supporting omitted props in the JavascriptCodeMirrorEditor component.

In `@packages/codemirror/src/components/JsonCodeMirrorEditor.tsx`:
- Around line 29-57: The component currently sets a default parameter
userExtensions = [] which creates a new array each render and breaks useMemo
stability for the extensions variable; remove the "= []" default from the
function signature so userExtensions can be nullable/undefined, then inside the
useMemo (where extensions is built) use a stable expression like
...(userExtensions ?? []) to spread extensions only when provided, ensuring
useMemo can properly memoize based on the userExtensions reference; update any
TypeScript prop typing for userExtensions to allow undefined/null if necessary.

In `@packages/codemirror/src/extensions/auto-trigger.ts`:
- Around line 20-26: The iterChanges callback currently continues processing
even after a quote is found; modify update.changes.iterChanges in
auto-trigger.ts to short-circuit the callback by checking the outer boolean flag
(insertedQuote) at the start of the callback and returning early if it's already
true, so once insertedQuote is set by the /^["']$/ test no further change bodies
are processed; ensure the outer variable (insertedQuote) is declared/initialized
before calling update.changes.iterChanges and used inside the callback to skip
work.

In `@packages/sql-editor/src/codemirror/utils/keyword-docs.ts`:
- Around line 6-17: getKeywordDocs currently re-merges
commonKeywords.default.keywords and duckdbKeywords.default.keywords on every
call; add a module-level cache (e.g., let cachedKeywordDocs:
Promise<Record<string, KeywordDoc>> | null) and return it if present to avoid
repeated spreads. In getKeywordDocs, if cachedKeywordDocs is null assign it to
the Promise that imports both JSONs and returns the merged object (using
commonKeywords and duckdbKeywords), otherwise return the cached promise; this
also ensures concurrent callers share the same in-flight load. Use the existing
symbols getKeywordDocs, commonKeywords, and duckdbKeywords to locate where to
add the cache.

In `@packages/sql-editor/src/codemirror/utils/schema-converter.ts`:
- Around line 8-10: The current loop assigns namespace[table.table.table] which
can collide for identical table names across schemas/catalogs; change the key to
a fully-qualified identifier using available metadata (e.g., include
table.table.schema and/or table.table.catalog) so each entry is unique (for
example build a key from table.table.catalog + '.' + table.table.schema + '.' +
table.table.table) while keeping the value as table.columns.map(col =>
col.name); update any consumers that expect the old key format if needed.

In `@packages/sql-editor/src/components/QueryEditorPanelEditor.tsx`:
- Around line 19-21: The code is directly accessing DuckDB state via
useStoreWithSqlEditor ((s) => s.db.tables and (s) => s.db.connector); replace
those direct accesses with the package's DuckDB-specific selectors (e.g.,
useDuckDb(), useSql(), or useDuckDbQuery()) so state access is decoupled.
Specifically, change the tableSchemas selector to use the DuckDB table/schema
selector (e.g., useDuckDb() or useDuckDbQuery() to get tables) and replace
connector usage with the DuckDB connector selector (e.g., useDuckDb().connector
or the dedicated connector selector); leave runQuery (parseAndRunQuery) wired to
the sql editor selector or migrate it to useSql() if available. Ensure you
import the appropriate hook(s) and remove direct (s).db.* references in
QueryEditorPanelEditor.tsx.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30165ff4-a32a-429c-890d-a67b577f211a

📥 Commits

Reviewing files that changed from the base of the PR and between bcd4f79 and 5b21d6b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (92)
  • examples/code-editor/README.md
  • examples/code-editor/eslint.config.js
  • examples/code-editor/index.html
  • examples/code-editor/package.json
  • examples/code-editor/src/App.tsx
  • examples/code-editor/src/EditorTypeSwitch.tsx
  • examples/code-editor/src/JavascriptEditor.tsx
  • examples/code-editor/src/JsonEditor.tsx
  • examples/code-editor/src/LanguageSwitch.tsx
  • examples/code-editor/src/SqlEditor.tsx
  • examples/code-editor/src/index.css
  • examples/code-editor/src/main.tsx
  • examples/code-editor/src/vite-env.d.ts
  • examples/code-editor/tsconfig.app.json
  • examples/code-editor/tsconfig.json
  • examples/code-editor/tsconfig.node.json
  • examples/code-editor/vite.config.ts
  • examples/multi-room/src/RoomPage.tsx
  • examples/notebook/src/NotebookPanel.tsx
  • packages/ai-core/package.json
  • packages/ai-core/src/components/AnalysisResult.tsx
  • packages/ai-core/src/components/PromptSuggestions.tsx
  • packages/ai-core/src/components/ToolPartRenderer.tsx
  • packages/ai-core/src/components/tools/ToolErrorMessage.tsx
  • packages/canvas/src/nodes/CanvasNodeContainer.tsx
  • packages/cells/eslint.config.js
  • packages/cells/src/components/InputCellContent.tsx
  • packages/cells/src/components/TextCellContent.tsx
  • packages/cells/src/components/VegaCellContent.tsx
  • packages/cells/typedoc.js
  • packages/codemirror/CHANGELOG.md
  • packages/codemirror/README.md
  • packages/codemirror/__tests__/validate-json-schema.test.ts
  • packages/codemirror/eslint.config.js
  • packages/codemirror/jest.config.js
  • packages/codemirror/package.json
  • packages/codemirror/src/components/CodeMirrorEditor.tsx
  • packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx
  • packages/codemirror/src/components/JsonCodeMirrorEditor.tsx
  • packages/codemirror/src/extensions/auto-trigger.ts
  • packages/codemirror/src/extensions/json-schema-autocomplete.ts
  • packages/codemirror/src/extensions/json-schema-lint.ts
  • packages/codemirror/src/index.ts
  • packages/codemirror/src/themes/base-theme.ts
  • packages/codemirror/src/themes/highlight-active-line-theme.ts
  • packages/codemirror/src/themes/json-theme.ts
  • packages/codemirror/src/themes/sqlrooms-theme.ts
  • packages/codemirror/src/utils/diagnostics-utils.ts
  • packages/codemirror/src/utils/validate-json-schema.ts
  • packages/codemirror/tsconfig.json
  • packages/codemirror/typedoc.js
  • packages/data-table/package.json
  • packages/data-table/src/useArrowDataTable.tsx
  • packages/duckdb-core/package.json
  • packages/duckdb-core/src/duckdb-function-suggestions.ts
  • packages/duckdb-core/src/index.ts
  • packages/duckdb/src/index.ts
  • packages/monaco-editor/README.md
  • packages/monaco-editor/package.json
  • packages/monaco-editor/src/components/MonacoEditor.tsx
  • packages/monaco-editor/src/index.ts
  • packages/monaco-editor/src/utils/color-utils.ts
  • packages/notebook/src/cellOperations/AddNewCellTabs.tsx
  • packages/notebook/src/cells/CellContainer.tsx
  • packages/preset-eslint/base.js
  • packages/room-shell/package.json
  • packages/room-shell/src/RoomShellCommandPalette.tsx
  • packages/room-store/__tests__/CommandSlice.test.ts
  • packages/room-store/src/CommandSlice.ts
  • packages/sql-editor/package.json
  • packages/sql-editor/src/SqlMonacoEditor.tsx
  • packages/sql-editor/src/codemirror/extensions/duck-db.ts
  • packages/sql-editor/src/codemirror/extensions/duckdb-completion.ts
  • packages/sql-editor/src/codemirror/extensions/duckdb-sql-extension.ts
  • packages/sql-editor/src/codemirror/extensions/duckdb-sql.ts
  • packages/sql-editor/src/codemirror/extensions/sql-keymap.ts
  • packages/sql-editor/src/codemirror/themes/sql-theme.ts
  • packages/sql-editor/src/codemirror/utils/keyword-docs.ts
  • packages/sql-editor/src/codemirror/utils/schema-converter.ts
  • packages/sql-editor/src/components/DuckdbCodeMirrorEditor.tsx
  • packages/sql-editor/src/components/QueryEditorPanelEditor.tsx
  • packages/sql-editor/src/index.ts
  • packages/ui/src/components/sidebar.tsx
  • packages/ui/src/hooks/use-mobile.ts
  • packages/ui/src/hooks/useIsDarkTheme.ts
  • packages/ui/src/hooks/useIsSystemPrefersDark.ts
  • packages/ui/src/index.ts
  • packages/ui/src/theme/theme-provider.tsx
  • packages/ui/tailwind-preset.css
  • packages/utils/__tests__/color.test.ts
  • packages/utils/src/color.ts
  • packages/utils/src/index.ts
💤 Files with no reviewable changes (3)
  • packages/monaco-editor/src/index.ts
  • packages/cells/typedoc.js
  • packages/cells/eslint.config.js

Comment thread examples/code-editor/package.json
Comment thread examples/code-editor/src/index.css
Comment thread examples/code-editor/src/JsonEditor.tsx
Comment thread examples/code-editor/src/SqlEditor.tsx
Comment thread examples/code-editor/vite.config.ts
Comment thread packages/codemirror/src/utils/validate-json-schema.ts Outdated
Comment thread packages/codemirror/src/utils/validate-json-schema.ts
Comment thread packages/data-table/src/useArrowDataTable.tsx Outdated
Comment thread packages/sql-editor/src/codemirror/extensions/duckdb-completion.ts Outdated
Comment thread packages/sql-editor/src/SqlCodeMirrorEditor.tsx

@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: 11

Caution

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

⚠️ Outside diff range comments (2)
packages/sql-editor/src/SqlMonacoEditor.tsx (1)

165-198: ⚠️ Potential issue | 🟡 Minor

Avoid duplicate function completions when connector is available.

This block adds connector-backed function suggestions on top of the existing DUCKDB_FUNCTIONS list, so overlapping DuckDB functions show up twice in autocomplete. Prefer deduping by label or skipping the static function branch when enriched connector results are present.

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

In `@packages/sql-editor/src/SqlMonacoEditor.tsx` around lines 165 - 198, The
autocomplete currently pushes static DUCKDB functions (the variable/functions
array) and then pushes connector-enriched functions from getFunctionSuggestions
when ctx.connector exists, causing duplicates; fix by deduplicating by label
before pushing: when ctx.connector is present call
getFunctionSuggestions(ctx.connector, word.word) first, collect the returned
names into a Set (e.g., connectorNames), then when iterating the static
functions array (functions) skip any func whose label/name exists in
connectorNames (or vice-versa choose to skip the static branch entirely if
connector results are non-empty); ensure this logic is applied around the
suggestions.push blocks so only unique labels are added.
packages/sql-editor/src/components/QueryEditorPanelEditor.tsx (1)

27-33: ⚠️ Potential issue | 🟠 Major

Don't discard the empty-string update.

SqlCodeMirrorEditor reports changes as strings, so clearing the editor now produces ''. The if (!value) return guard drops that update, which prevents the controlled query from ever being cleared.

🔧 Minimal fix
   const handleUpdateQuery = useCallback(
-    (value: string | undefined) => {
-      if (!value) return;
+    (value: string) => {
       updateQueryText(queryId, value);
     },
     [queryId, updateQueryText],
   );

Also applies to: 45-49

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

In `@packages/sql-editor/src/components/QueryEditorPanelEditor.tsx` around lines
27 - 33, The change handler handleUpdateQuery currently ignores empty-string
updates due to the guard "if (!value) return", so clearing the
SqlCodeMirrorEditor (which emits '') won't update the controlled query; remove
or change that falsy check to allow '' (e.g., check for value === undefined or
value == null instead) and ensure you still call updateQueryText(queryId, value)
when value is '' ; apply the same fix to the other analogous update handler in
this file that uses the same falsy guard so empty-string editor clears are
propagated.
♻️ Duplicate comments (2)
packages/sql-editor/src/SqlCodeMirrorEditor.tsx (1)

58-64: ⚠️ Potential issue | 🟠 Major

getLatestSchemas is being cached, so schema-aware features can go stale.

If the caller keeps a stable getLatestSchemas callback, this memo freezes tableSchemas until the callback identity changes. That leaves completions and hover data outdated after schema updates.

🔧 Minimal fix
-  const currentSchemas = useMemo(() => {
-    if (getLatestSchemas) {
-      return getLatestSchemas().tableSchemas;
-    }
-    return tableSchemas;
-  }, [getLatestSchemas, tableSchemas]);
+  const currentSchemas = getLatestSchemas?.().tableSchemas ?? tableSchemas;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/SqlCodeMirrorEditor.tsx` around lines 58 - 64, The
memoization of getLatestSchemas in the currentSchemas useMemo freezes
tableSchemas when the getLatestSchemas callback identity is stable, causing
stale completions/hover info; change currentSchemas to call getLatestSchemas()
(or prefer getLatestSchemas().tableSchemas) on each render when getLatestSchemas
is provided instead of caching it in useMemo, or include tableSchemas and any
dynamic values returned by getLatestSchemas in the dependency array so
currentSchemas always reflects updated schemas (update the useMemo/logic around
getLatestSchemas and tableSchemas in SqlCodeMirrorEditor.tsx accordingly).
packages/sql-editor/src/SqlMonacoEditor.tsx (1)

354-375: ⚠️ Potential issue | 🟠 Major

Shortcut binding still captures a stale onRunQuery.

This listener is only registered during the initial mount. If onRunQuery changes later—or starts as undefined and is provided after mount—Ctrl/Cmd+Enter will keep calling the old callback or never bind at all.

🔧 Minimal fix
+  const onRunQueryRef = useRef(onRunQuery);
+
+  useEffect(() => {
+    onRunQueryRef.current = onRunQuery;
+  }, [onRunQuery]);
+
   const handleEditorDidMount = useCallback<OnMount>(
     (editor, monaco) => {
       ensureSqlLanguageConfigured(monaco);
       ensureSqlCompletionProvider(monaco);
@@
-      if (onRunQuery) {
-        editor.onKeyDown((e) => {
+      const keydownDisposable = editor.onKeyDown((e) => {
+        const runQuery = onRunQueryRef.current;
+        if (!runQuery) return;
+
           if ((e.ctrlKey || e.metaKey) && e.keyCode === monaco.KeyCode.Enter) {
             e.preventDefault();
             e.stopPropagation();
@@
-            onRunQuery({
+            runQuery({
               value,
               selectedValue,
               isSelectionEmpty,
             });
           }
-        });
-      }
+        });
@@
       editor.onDidDispose(() => {
+        keydownDisposable.dispose();
         modelChangeDisposable?.dispose?.();
         const model = modelRef.current;
         if (model) sqlCompletionContextByModel.delete(model);
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/SqlMonacoEditor.tsx` around lines 354 - 375, The
keyboard shortcut handler registered via editor.onKeyDown captures a stale
onRunQuery closure because it's only added once at mount; update the
registration so the listener always uses the latest onRunQuery by either (a)
moving the editor.onKeyDown registration into a useEffect that depends on
onRunQuery and disposes the previous listener on cleanup, or (b) keep a stable
handler that reads a ref to the current onRunQuery; ensure you call dispose() on
the returned disposable when unmounting or when onRunQuery changes so
editor.onKeyDown doesn't call an outdated or undefined callback (refer to
editor.onKeyDown, onRunQuery, editor.getModel, editor.getSelection).
🧹 Nitpick comments (5)
packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx (1)

29-35: Simplify options merging pattern.

The current pattern mixes nullish coalescing defaults with a trailing spread, which creates redundant logic. The ?? operators on lines 30, 31, and 33 are effectively ignored when those keys exist in options because ...options overwrites them afterward.

Additionally, autocompletion: true on line 32 can be overridden by ...options if the user passes autocompletion: false.

♻️ Proposed fix: Use a cleaner defaults-then-override pattern
       options={{
-        lineNumbers: options.lineNumbers ?? false,
-        lineWrapping: options.lineWrapping ?? true,
-        autocompletion: true,
-        foldGutter: options.foldGutter ?? false,
+        lineNumbers: false,
+        lineWrapping: true,
+        autocompletion: true,
+        foldGutter: false,
         ...options,
       }}

This pattern clearly sets defaults first, then allows user options to override them. If autocompletion should always be true and not be overridable, move it after the spread:

options={{
  lineNumbers: false,
  lineWrapping: true,
  foldGutter: false,
  ...options,
  autocompletion: true, // force-enabled
}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx` around
lines 29 - 35, The options merging is redundant and order-sensitive: remove the
per-key nullish coalescing and instead supply sensible defaults first, then
spread the incoming options (e.g., set default lineNumbers: false, lineWrapping:
true, foldGutter: false, ...options) so user options override defaults; if
autocompletion must be forced true, place autocompletion after the spread (or if
it should be user-configurable leave it before the spread). Update the options
object in JavascriptCodeMirrorEditor (the options prop construction)
accordingly.
packages/codemirror/src/components/JsonCodeMirrorEditor.tsx (1)

41-49: Gate quote-triggered completion behind actual completion support.

autoTriggerOnQuote() is added unconditionally, but jsonSchemaAutocomplete(schema) (the only completion source for this component) is only registered when a schema is provided. In the no-schema path, startCompletion() is called on every quote character but has no completion source to work with, causing unnecessary internal dispatch even though CodeMirror won't display a UI. Only add the trigger when schema-driven completion is enabled, or when an explicit completion extension is passed in userExtensions.

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

In `@packages/codemirror/src/components/JsonCodeMirrorEditor.tsx` around lines 41
- 49, The unconditional autoTriggerOnQuote() causes startCompletion() to fire
with no completion source when jsonSchemaAutocomplete(schema) isn't registered;
update the extensions assembly so autoTriggerOnQuote() is only included when
either a schema is provided (jsonSchemaAutocomplete(schema) is present) or when
userExtensions contains an explicit completion provider. Concretely, inside the
component where extensions are built, wrap autoTriggerOnQuote() behind a
condition: include it if schema is truthy OR if a helper check (e.g.,
hasCompletionExtension(userExtensions)) detects a completion-related extension
in userExtensions (match on known completion extension shapes or metadata),
otherwise omit autoTriggerOnQuote().
packages/sql-editor/src/codemirror/themes/sql-tooltip-theme.ts (1)

9-25: Theme value is captured once at creation time.

The dark: theme === 'dark' parameter is fixed when createSqlTooltipTheme() is called. If the user toggles between light/dark mode while the editor is open, this won't update. Since the actual colors use CSS variables (--color-popover), the styling should adapt, but CodeMirror's internal dark mode flag will remain stale.

If dynamic theme switching is supported, consider either:

  1. Recreating the extension when theme changes, or
  2. Relying solely on CSS variables without the dark parameter if it doesn't affect tooltip behavior.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/codemirror/themes/sql-tooltip-theme.ts` around lines
9 - 25, The theme boolean is captured once by getTheme() in
createSqlTooltipTheme(), so EditorView.theme(..., {dark: theme === 'dark'}) will
become stale if the user toggles light/dark; to fix, stop baking the dark flag
into the extension—remove the second argument (or pass no {dark: ...}) from the
EditorView.theme call so the tooltip styling relies on CSS variables;
alternatively, if you prefer explicit reactivity, recreate the extension when
getTheme() changes (i.e., regenerate createSqlTooltipTheme() and reconfigure the
editor) rather than using a single captured theme value.
packages/sql-editor/src/codemirror/extensions/hover.ts (1)

50-54: Consider using DOM-based rendering instead of innerHTML.

Static analysis flagged the innerHTML assignment as a potential XSS vector. While the risk is mitigated because renderComponentToString renders a controlled React component with internally-sourced function documentation, using innerHTML is still a fragile pattern. If FunctionDocumentation ever interpolates user-controlled data (e.g., function names from the editor), it could become exploitable.

Consider rendering the React component directly to the DOM element using createRoot:

♻️ Proposed safer rendering approach
+import {createRoot} from 'react-dom/client';
-import {renderComponentToString} from '@sqlrooms/utils';
 
 // Inside the tooltip creation:
       const dom = document.createElement('div');
-      dom.innerHTML = renderComponentToString(FunctionDocumentation, {
-        functions: functionGroup.overloads,
-      });
+      const root = createRoot(dom);
+      root.render(<FunctionDocumentation functions={functionGroup.overloads} />);

       return {
         pos: node.from,
         end: node.to,
         above: true,
-        create: () => ({dom}),
+        create: () => ({
+          dom,
+          destroy: () => root.unmount(),
+        }),
       };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/codemirror/extensions/hover.ts` around lines 50 - 54,
Replace the unsafe innerHTML assignment that uses renderComponentToString by
mounting the React component into the tooltip DOM using React's createRoot API:
create a DOM element (dom), call createRoot(dom).render with the
FunctionDocumentation component and its functions prop
(functionGroup.overloads), import createRoot from 'react-dom/client', and ensure
you track the created root to call root.unmount when the tooltip is removed so
we don't leak mounts; this replaces renderComponentToString + dom.innerHTML and
prevents XSS via string HTML injection.
packages/sql-editor/src/codemirror/extensions/completion.ts (1)

56-59: Same innerHTML concern as hover.ts.

This has the same pattern of setting innerHTML from renderComponentToString. Consider the same DOM-based rendering approach suggested for hover.ts.

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

In `@packages/sql-editor/src/codemirror/extensions/completion.ts` around lines 56
- 59, The completion popup currently sets unsafe HTML by assigning
renderComponentToString(FunctionDocumentation...) to dom.innerHTML; instead,
change this to a DOM-based render like in hover.ts: create a container element
(or DocumentFragment), programmatically build or mount the FunctionDocumentation
markup as DOM nodes (using the same helper used in hover.ts), and append those
nodes to the existing dom variable instead of assigning innerHTML; update the
code around the const dom and renderComponentToString(FunctionDocumentation, {
functions: overloads }) call to produce and attach safe DOM children (or use the
hover.ts DOM-render helper) to avoid injecting raw HTML.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cells/eslint.config.js`:
- Around line 3-5: The file exports a flat-config array but the JSDoc uses
Linter.Config (singular); update the top JSDoc to reflect an array type such as
import("eslint").Linter.Config[] (or the appropriate FlatConfig type) so
editor/type-checker sees that export default [...] is an array of config
objects; modify the JSDoc comment above the export (the line with /** `@type`
{import("eslint").Linter.Config} */) to use the array type while leaving the
existing export default [...config,] and config identifier unchanged.

In `@packages/codemirror/src/components/JsonCodeMirrorEditor.tsx`:
- Around line 16-17: The component JsonCodeMirrorEditor currently types the prop
value as `string | object` and directly JSON.stringify()s it during render which
will throw for circular refs or non-JSON-safe values; update the prop type to a
JSON-safe type (e.g., JSONValue union: null | string | number | boolean |
JSONObject | JSONArray) or else wrap the serialization in a safeSerializer with
try/catch inside the component (e.g., in the render path where `value` is used)
to fall back to a safe representation (original string, a preview, or an
error-safe placeholder) and avoid throwing; adjust the prop/type alias and the
serialization logic used by JsonCodeMirrorEditor so any runtime serialization
failure is caught and handled gracefully.

In `@packages/duckdb-core/src/duckdb-function-utils.ts`:
- Around line 48-49: The code calls Array.from(parameters) and
Array.from(parameterTypes) which will throw if those values are null/undefined;
update the serialization in duckdb-function-utils by guarding these fields
(e.g., use parameters == null ? null : Array.from(parameters) and parameterTypes
== null ? null : Array.from(parameterTypes), or the equivalent nullish checks)
so nullable DuckDB functions without parameters remain null instead of causing a
runtime error, referencing the parameters and parameterTypes symbols to locate
the change.

In `@packages/duckdb-core/src/get-function-suggestions.ts`:
- Around line 15-33: The SQL builds a query with the variable limit interpolated
directly into connector.query, which can allow non-numeric or malicious input;
before calling connector.query (in the get-function-suggestions query block),
validate and sanitize limit by coercing to a safe integer (e.g., const safeLimit
= Math.max(1, Math.min(Number(limit) || 0, MAX_LIMIT))) or parseInt and clamp to
a reasonable MAX_LIMIT, and then use safeLimit in the SQL string (or better, use
a parameterized query for limit if connector.query supports parameters) so only
a validated numeric value is inserted into the SQL.

In `@packages/monaco-editor/src/components/MonacoEditor.tsx`:
- Line 169: Replace the non-reactive getTheme() call with the reactive
useTheme() hook so the component re-renders on theme changes: import and call
useTheme() (from theme-provider) in MonacoEditor, derive isDark from the
returned theme value (e.g., theme === 'dark'), and update the existing effect
dependency to use that reactive value (isDark or theme) instead of the one-time
getTheme() result; if you intentionally want non-reactive behavior,
alternatively document this limitation or add a consumer-facing key/refresh
mechanism to MonacoEditor.
- Around line 63-65: Update the README entry that documents the MonacoEditor
component's theme prop to include the missing 'system' option: find the Theme
type and the theme?: Theme prop in MonacoEditor (where Theme = 'dark' | 'light'
| 'system') and add 'system' to the listed supported values and any usage
examples or prop tables so the docs match the component API.

In `@packages/sql-editor/src/codemirror/extensions/completion.ts`:
- Line 19: The WeakMap completionContextByView is never populated and therefore
dead code; either remove it or populate it correctly—preferably remove the
unused declaration and any lookups that reference completionContextByView (so
the code relies on the existing local/context variable), or if the original
intent was to cache per-view CompletionContext then add logic to set
completionContextByView.set(view, ctx) whenever a view's CompletionContext is
created or updated (ensure lookups use completionContextByView.get(view)
consistently). Target symbols: completionContextByView and CompletionContext
(and any lookup that falls back to context).

In `@packages/sql-editor/src/components/FunctionDocumentation.tsx`:
- Around line 72-75: The JSX in the FunctionDocumentation component always
renders "overload" regardless of count; update the JSX that uses
moreOverloadsCount to pluralize the label (e.g., use a conditional or plural
helper: moreOverloadsCount === 1 ? 'overload' : 'overloads') so the rendered
text becomes "(+N more overload)" for 1 and "(+N more overloads)" for any other
count; locate the fragment that references moreOverloadsCount in
FunctionDocumentation.tsx and replace the static string with the conditional
pluralization.
- Around line 88-90: The current guard in the FunctionDocumentation component
that returns null when (!parameterTypes && !parameters && !returnType)
suppresses the function signature entirely; update the render logic so that when
metadata is partial/missing you still render a fallback signature using the
function name (e.g., render name() or `${name}()`), instead of returning null.
Locate the conditional in FunctionDocumentation (the block checking
parameterTypes/parameters/returnType) and replace the early return with code
that builds and renders a minimal signature from the name prop (empty parameter
list and no return type) while keeping the existing richer rendering when
metadata is present.

In `@packages/sql-editor/src/components/SqlColumnTooltip.tsx`:
- Around line 58-71: The lookup currently uses case-sensitive comparisons for
databaseName, schemaName, tableName and columnName against table.table and
column.name; change the comparisons in the loop (the checks that reference
databaseName, schemaName, tableName and the column lookup that uses
table.columns.find) to perform case-insensitive matching (e.g., normalize both
sides with toLowerCase() or a locale-insensitive compare) so that identifiers
are matched regardless of casing; ensure you handle potential undefined values
safely before normalizing.

In `@packages/sql-editor/src/components/SqlTableTooltip.tsx`:
- Line 16: The table lookup in SqlTableTooltip.tsx currently uses
tables.find((table) => table.table.table === tableName) which fails for schema-
or db-qualified names; parse the incoming qualified tableName (from data.word)
into database/schema/table components (like SqlColumnTooltip.tsx does) or reuse
its findTable-style logic, then match against table.table.database,
table.table.schema and table.table.table (or the equivalent properties) to
locate the correct entry; update the tables.find call and any dependent variable
names to use the parsed parts so qualified names resolve correctly.

---

Outside diff comments:
In `@packages/sql-editor/src/components/QueryEditorPanelEditor.tsx`:
- Around line 27-33: The change handler handleUpdateQuery currently ignores
empty-string updates due to the guard "if (!value) return", so clearing the
SqlCodeMirrorEditor (which emits '') won't update the controlled query; remove
or change that falsy check to allow '' (e.g., check for value === undefined or
value == null instead) and ensure you still call updateQueryText(queryId, value)
when value is '' ; apply the same fix to the other analogous update handler in
this file that uses the same falsy guard so empty-string editor clears are
propagated.

In `@packages/sql-editor/src/SqlMonacoEditor.tsx`:
- Around line 165-198: The autocomplete currently pushes static DUCKDB functions
(the variable/functions array) and then pushes connector-enriched functions from
getFunctionSuggestions when ctx.connector exists, causing duplicates; fix by
deduplicating by label before pushing: when ctx.connector is present call
getFunctionSuggestions(ctx.connector, word.word) first, collect the returned
names into a Set (e.g., connectorNames), then when iterating the static
functions array (functions) skip any func whose label/name exists in
connectorNames (or vice-versa choose to skip the static branch entirely if
connector results are non-empty); ensure this logic is applied around the
suggestions.push blocks so only unique labels are added.

---

Duplicate comments:
In `@packages/sql-editor/src/SqlCodeMirrorEditor.tsx`:
- Around line 58-64: The memoization of getLatestSchemas in the currentSchemas
useMemo freezes tableSchemas when the getLatestSchemas callback identity is
stable, causing stale completions/hover info; change currentSchemas to call
getLatestSchemas() (or prefer getLatestSchemas().tableSchemas) on each render
when getLatestSchemas is provided instead of caching it in useMemo, or include
tableSchemas and any dynamic values returned by getLatestSchemas in the
dependency array so currentSchemas always reflects updated schemas (update the
useMemo/logic around getLatestSchemas and tableSchemas in
SqlCodeMirrorEditor.tsx accordingly).

In `@packages/sql-editor/src/SqlMonacoEditor.tsx`:
- Around line 354-375: The keyboard shortcut handler registered via
editor.onKeyDown captures a stale onRunQuery closure because it's only added
once at mount; update the registration so the listener always uses the latest
onRunQuery by either (a) moving the editor.onKeyDown registration into a
useEffect that depends on onRunQuery and disposes the previous listener on
cleanup, or (b) keep a stable handler that reads a ref to the current
onRunQuery; ensure you call dispose() on the returned disposable when unmounting
or when onRunQuery changes so editor.onKeyDown doesn't call an outdated or
undefined callback (refer to editor.onKeyDown, onRunQuery, editor.getModel,
editor.getSelection).

---

Nitpick comments:
In `@packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx`:
- Around line 29-35: The options merging is redundant and order-sensitive:
remove the per-key nullish coalescing and instead supply sensible defaults
first, then spread the incoming options (e.g., set default lineNumbers: false,
lineWrapping: true, foldGutter: false, ...options) so user options override
defaults; if autocompletion must be forced true, place autocompletion after the
spread (or if it should be user-configurable leave it before the spread). Update
the options object in JavascriptCodeMirrorEditor (the options prop construction)
accordingly.

In `@packages/codemirror/src/components/JsonCodeMirrorEditor.tsx`:
- Around line 41-49: The unconditional autoTriggerOnQuote() causes
startCompletion() to fire with no completion source when
jsonSchemaAutocomplete(schema) isn't registered; update the extensions assembly
so autoTriggerOnQuote() is only included when either a schema is provided
(jsonSchemaAutocomplete(schema) is present) or when userExtensions contains an
explicit completion provider. Concretely, inside the component where extensions
are built, wrap autoTriggerOnQuote() behind a condition: include it if schema is
truthy OR if a helper check (e.g., hasCompletionExtension(userExtensions))
detects a completion-related extension in userExtensions (match on known
completion extension shapes or metadata), otherwise omit autoTriggerOnQuote().

In `@packages/sql-editor/src/codemirror/extensions/completion.ts`:
- Around line 56-59: The completion popup currently sets unsafe HTML by
assigning renderComponentToString(FunctionDocumentation...) to dom.innerHTML;
instead, change this to a DOM-based render like in hover.ts: create a container
element (or DocumentFragment), programmatically build or mount the
FunctionDocumentation markup as DOM nodes (using the same helper used in
hover.ts), and append those nodes to the existing dom variable instead of
assigning innerHTML; update the code around the const dom and
renderComponentToString(FunctionDocumentation, { functions: overloads }) call to
produce and attach safe DOM children (or use the hover.ts DOM-render helper) to
avoid injecting raw HTML.

In `@packages/sql-editor/src/codemirror/extensions/hover.ts`:
- Around line 50-54: Replace the unsafe innerHTML assignment that uses
renderComponentToString by mounting the React component into the tooltip DOM
using React's createRoot API: create a DOM element (dom), call
createRoot(dom).render with the FunctionDocumentation component and its
functions prop (functionGroup.overloads), import createRoot from
'react-dom/client', and ensure you track the created root to call root.unmount
when the tooltip is removed so we don't leak mounts; this replaces
renderComponentToString + dom.innerHTML and prevents XSS via string HTML
injection.

In `@packages/sql-editor/src/codemirror/themes/sql-tooltip-theme.ts`:
- Around line 9-25: The theme boolean is captured once by getTheme() in
createSqlTooltipTheme(), so EditorView.theme(..., {dark: theme === 'dark'}) will
become stale if the user toggles light/dark; to fix, stop baking the dark flag
into the extension—remove the second argument (or pass no {dark: ...}) from the
EditorView.theme call so the tooltip styling relies on CSS variables;
alternatively, if you prefer explicit reactivity, recreate the extension when
getTheme() changes (i.e., regenerate createSqlTooltipTheme() and reconfigure the
editor) rather than using a single captured theme value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2a072482-9f46-47df-b9e1-88c92a0ebe91

📥 Commits

Reviewing files that changed from the base of the PR and between 4a057a0 and 73789cb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (40)
  • examples/code-editor/src/SqlEditor.tsx
  • packages/cells/__tests__/mocks/sql-editor.js
  • packages/cells/eslint.config.js
  • packages/cells/package.json
  • packages/cells/src/components/SqlCellContent.tsx
  • packages/codemirror/README.md
  • packages/codemirror/src/components/JavascriptCodeMirrorEditor.tsx
  • packages/codemirror/src/components/JsonCodeMirrorEditor.tsx
  • packages/codemirror/src/themes/base-theme.ts
  • packages/db/src/index.ts
  • packages/duckdb-core/src/duckdb-function-utils.ts
  • packages/duckdb-core/src/get-function-documentation.ts
  • packages/duckdb-core/src/get-function-suggestions.ts
  • packages/duckdb-core/src/index.ts
  • packages/duckdb/src/index.ts
  • packages/monaco-editor/src/components/MonacoEditor.tsx
  • packages/sql-editor/package.json
  • packages/sql-editor/src/SqlCodeMirrorEditor.tsx
  • packages/sql-editor/src/SqlMonacoEditor.tsx
  • packages/sql-editor/src/codemirror/extensions/completion.ts
  • packages/sql-editor/src/codemirror/extensions/create-sql-extension.ts
  • packages/sql-editor/src/codemirror/extensions/duck-db/duck-db.ts
  • packages/sql-editor/src/codemirror/extensions/duck-db/duckdb-keywords.ts
  • packages/sql-editor/src/codemirror/extensions/duck-db/duckdb-sql-extension.ts
  • packages/sql-editor/src/codemirror/extensions/duck-db/duckdb-sql.ts
  • packages/sql-editor/src/codemirror/extensions/duck-db/index.ts
  • packages/sql-editor/src/codemirror/extensions/hover.ts
  • packages/sql-editor/src/codemirror/themes/sql-theme.ts
  • packages/sql-editor/src/codemirror/themes/sql-tooltip-theme.ts
  • packages/sql-editor/src/components/CreateTableModal.tsx
  • packages/sql-editor/src/components/FunctionDocumentation.tsx
  • packages/sql-editor/src/components/QueryEditorPanelEditor.tsx
  • packages/sql-editor/src/components/SqlColumnTooltip.tsx
  • packages/sql-editor/src/components/SqlColumnsTable.tsx
  • packages/sql-editor/src/components/SqlTableTooltip.tsx
  • packages/sql-editor/src/index.ts
  • packages/utils/src/index.ts
  • packages/utils/src/renderComponentToString.tsx
  • packages/utils/tsconfig.json
  • packages/vega/src/editor/VegaSqlEditorPanel.tsx
💤 Files with no reviewable changes (1)
  • packages/cells/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/codemirror/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/sql-editor/src/codemirror/themes/sql-theme.ts
  • examples/code-editor/src/SqlEditor.tsx
  • packages/codemirror/src/themes/base-theme.ts

Comment thread packages/cells/eslint.config.js Outdated
Comment thread packages/codemirror/src/components/JsonCodeMirrorEditor.tsx
Comment thread packages/duckdb-core/src/duckdb-function-utils.ts Outdated
Comment thread packages/duckdb-core/src/get-function-suggestions.ts
Comment thread packages/monaco-editor/src/components/MonacoEditor.tsx
Comment thread packages/sql-editor/src/codemirror/extensions/completion.ts Outdated
Comment thread packages/sql-editor/src/components/FunctionDocumentation.tsx
Comment thread packages/sql-editor/src/components/FunctionDocumentation.tsx Outdated
Comment thread packages/sql-editor/src/components/SqlColumnTooltip.tsx Outdated
Comment thread packages/sql-editor/src/components/SqlTableTooltip.tsx Outdated

@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: 3

♻️ Duplicate comments (1)
examples/code-editor/src/SqlEditor.tsx (1)

22-25: ⚠️ Potential issue | 🟡 Minor

Populate the nested qualified table names.

These fixtures still leave table.table.schema undefined, so schema-aware consumers get a partial QualifiedTableName even though the outer DataTable says the tables live in main. Please fill the nested schema field too, and make toString() match the qualified name.

🔧 Proposed fix
   {
     table: {
+      schema: 'main',
       table: 'users',
-      toString: () => 'users',
+      toString: () => 'main.users',
     },
@@
   {
     table: {
+      schema: 'main',
       table: 'orders',
-      toString: () => 'orders',
+      toString: () => 'main.orders',
     },

Also applies to: 39-42

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

In `@examples/code-editor/src/SqlEditor.tsx` around lines 22 - 25, The nested
qualified table objects are missing their schema and have incorrect toString
values; update the inner table object so its schema is set to 'main' (i.e.,
populate table.table.schema = 'main') and change the toString() implementations
to return the fully qualified name ('main.users' and the other table's
'main.<name>') so consumers receive a complete QualifiedTableName; apply this
change for the table object at the shown location and the similar table object
at lines 39-42.
🧹 Nitpick comments (1)
packages/duckdb-core/src/get-function-suggestions.ts (1)

44-45: Consider more efficient caching for autocomplete queries.

The memoizeOnce utility caches only the most recent call. Since this function is called with different wordBeforeCursor values as the user types (e.g., "f" → "fu" → "fun"), each keystroke invalidates the cache and triggers a new query. An LRU cache keyed by wordBeforeCursor would retain results for earlier partial matches, improving responsiveness for common autocomplete patterns (e.g., when users backtrack or correct typos).

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

In `@packages/duckdb-core/src/get-function-suggestions.ts` around lines 44 - 45,
The current export uses memoizeOnce which only caches the last call, causing
frequent cache misses as users type; replace this with an LRU-style memoization
keyed by the function input (wordBeforeCursor) so previous partial-query results
are retained; update the export of getFunctionSuggestions to wrap
getFunctionSuggestionsImpl with an LRU memoizer (e.g., memoizeLRU or lru-cache)
with a small bounded size (e.g., 32–100) and appropriate keying based on the
wordBeforeCursor parameter to avoid unbounded memory growth and improve
autocomplete responsiveness.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/code-editor/src/SqlEditor.tsx`:
- Around line 68-80: The Monaco branch is not passing the onRunQuery callback to
SqlMonacoEditor, so Cmd/Ctrl+Enter works only in SqlCodeMirrorEditor; update the
monaco return to forward the onRunQuery prop (the same function passed to
SqlCodeMirrorEditor) into the SqlMonacoEditor component so its built-in
Cmd/Ctrl+Enter handler can invoke it (ensure the prop name onRunQuery and the
handler signature match the existing SqlMonacoEditor implementation).

In `@packages/sql-editor/src/SqlMonacoEditor.tsx`:
- Around line 361-382: The keyboard handler registered via editor.onKeyDown
closes over the stale onRunQuery prop; update the handler to call
onRunQueryRef.current(...) instead of onRunQuery, and remove onRunQuery from the
handleEditorDidMount dependency array so updates are driven by the ref; also
capture the disposable returned by editor.onKeyDown (in handleEditorDidMount)
and ensure you dispose it when unmounting or before re-registering to avoid
duplicate listeners.

In `@packages/sql-editor/src/utils/qualified-name-parser.ts`:
- Around line 83-129: findTableAndColumn currently treats a bare columnName as
"first match wins", causing ambiguous results when multiple tables contain the
same column; change the logic in findTableAndColumn to collect all matching
{table, column} pairs (searching tables by columnName using the existing
localeCompare logic) and if more than one match is found when
tableName/schemaName/databaseName are all undefined, return undefined (only
return the single match if matches.length === 1). Use the existing symbols
DataTable, TableColumn, columnName, tableName, schemaName, databaseName and the
function findTableAndColumn to locate where to replace the one-pass return with
the collection-and-disambiguation approach.

---

Duplicate comments:
In `@examples/code-editor/src/SqlEditor.tsx`:
- Around line 22-25: The nested qualified table objects are missing their schema
and have incorrect toString values; update the inner table object so its schema
is set to 'main' (i.e., populate table.table.schema = 'main') and change the
toString() implementations to return the fully qualified name ('main.users' and
the other table's 'main.<name>') so consumers receive a complete
QualifiedTableName; apply this change for the table object at the shown location
and the similar table object at lines 39-42.

---

Nitpick comments:
In `@packages/duckdb-core/src/get-function-suggestions.ts`:
- Around line 44-45: The current export uses memoizeOnce which only caches the
last call, causing frequent cache misses as users type; replace this with an
LRU-style memoization keyed by the function input (wordBeforeCursor) so previous
partial-query results are retained; update the export of getFunctionSuggestions
to wrap getFunctionSuggestionsImpl with an LRU memoizer (e.g., memoizeLRU or
lru-cache) with a small bounded size (e.g., 32–100) and appropriate keying based
on the wordBeforeCursor parameter to avoid unbounded memory growth and improve
autocomplete responsiveness.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6886cd4e-1b71-4d25-8968-b8d1862fc4d4

📥 Commits

Reviewing files that changed from the base of the PR and between 73789cb and a4e4984.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • apps/sqlrooms-cli-ui/src/components/DataSourcesPanel.tsx
  • examples/code-editor/src/SqlEditor.tsx
  • packages/cells/package.json
  • packages/cells/src/components/InputCellContent.tsx
  • packages/cells/src/components/SqlCellContent.tsx
  • packages/codemirror/src/themes/base-theme.ts
  • packages/duckdb-core/src/duckdb-function-utils.ts
  • packages/duckdb-core/src/get-function-suggestions.ts
  • packages/monaco-editor/README.md
  • packages/monaco-editor/src/components/MonacoEditor.tsx
  • packages/sql-editor/src/SqlMonacoEditor.tsx
  • packages/sql-editor/src/codemirror/extensions/completion.ts
  • packages/sql-editor/src/codemirror/extensions/hover.ts
  • packages/sql-editor/src/components/FunctionDocumentation.tsx
  • packages/sql-editor/src/components/SqlColumnTooltip.tsx
  • packages/sql-editor/src/components/SqlTableTooltip.tsx
  • packages/sql-editor/src/utils/qualified-name-parser.ts
💤 Files with no reviewable changes (1)
  • packages/cells/package.json
✅ Files skipped from review due to trivial changes (1)
  • apps/sqlrooms-cli-ui/src/components/DataSourcesPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/cells/src/components/SqlCellContent.tsx
  • packages/sql-editor/src/components/SqlTableTooltip.tsx
  • packages/cells/src/components/InputCellContent.tsx
  • packages/sql-editor/src/components/SqlColumnTooltip.tsx
  • packages/sql-editor/src/components/FunctionDocumentation.tsx
  • packages/codemirror/src/themes/base-theme.ts

Comment on lines +68 to +80
if (editorType === 'monaco') {
return (
<SqlMonacoEditor
tableSchemas={tableSchemas}
value={sqlValue}
onChange={(value: string | undefined) => setSqlValue(value || '')}
className={className}
options={{
minimap: {enabled: false},
lineNumbers: 'on',
wordWrap: 'on',
}}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SqlMonacoEditor props =="
sed -n '1,120p' packages/sql-editor/src/SqlMonacoEditor.tsx

echo
echo "== Example SqlEditor wiring =="
sed -n '60,110p' examples/code-editor/src/SqlEditor.tsx

echo
echo "== onRunQuery usages =="
rg -n -C2 '\bonRunQuery\b' packages/sql-editor/src examples/code-editor/src

Repository: sqlrooms/sqlrooms

Length of output: 11230


Wire onRunQuery through the Monaco branch to match CodeMirror behavior.

The SqlMonacoEditor component supports the onRunQuery prop and implements Cmd/Ctrl+Enter handling, but the example passes it only to SqlCodeMirrorEditor. This creates inconsistent behavior where the query-execution keyboard shortcut works in CodeMirror mode but not in Monaco mode.

🔧 Proposed fix
     return (
       <SqlMonacoEditor
         tableSchemas={tableSchemas}
         value={sqlValue}
         onChange={(value: string | undefined) => setSqlValue(value || '')}
+        onRunQuery={({value, selectedValue, isSelectionEmpty}) =>
+          handleRunQuery(isSelectionEmpty ? value : selectedValue)
+        }
         className={className}
         options={{
           minimap: {enabled: false},
           lineNumbers: 'on',
           wordWrap: 'on',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/code-editor/src/SqlEditor.tsx` around lines 68 - 80, The Monaco
branch is not passing the onRunQuery callback to SqlMonacoEditor, so
Cmd/Ctrl+Enter works only in SqlCodeMirrorEditor; update the monaco return to
forward the onRunQuery prop (the same function passed to SqlCodeMirrorEditor)
into the SqlMonacoEditor component so its built-in Cmd/Ctrl+Enter handler can
invoke it (ensure the prop name onRunQuery and the handler signature match the
existing SqlMonacoEditor implementation).

Comment on lines +361 to +382
// Add keyboard shortcut for running query
if (onRunQuery) {
editor.onKeyDown((e) => {
if ((e.ctrlKey || e.metaKey) && e.keyCode === monaco.KeyCode.Enter) {
e.preventDefault();
e.stopPropagation();

const model = editor.getModel();
const selection = editor.getSelection();
const value = editor.getValue();
const selectedValue =
model && selection ? model.getValueInRange(selection) : '';
const isSelectionEmpty = !selection || selection.isEmpty();

onRunQuery({
value,
selectedValue,
isSelectionEmpty,
});
}
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stale callback issue persists: handler uses onRunQuery instead of onRunQueryRef.current.

The ref pattern was added (lines 299-303) to prevent stale closures, but the keyboard handler at line 375 still calls onRunQuery directly from the closure instead of onRunQueryRef.current. Since handleEditorDidMount is memoized with onRunQuery in its dependency array (line 402), the handler will be recreated anyway—but the editor.onKeyDown listener registered inside won't be disposed and re-registered, leading to potential stale callbacks or multiple registrations.

🔧 Proposed fix
       // Add keyboard shortcut for running query
-      if (onRunQuery) {
-        editor.onKeyDown((e) => {
+      const keydownDisposable = editor.onKeyDown((e) => {
+          const runQuery = onRunQueryRef.current;
+          if (!runQuery) return;
           if ((e.ctrlKey || e.metaKey) && e.keyCode === monaco.KeyCode.Enter) {
             e.preventDefault();
             e.stopPropagation();

             const model = editor.getModel();
             const selection = editor.getSelection();
             const value = editor.getValue();
             const selectedValue =
               model && selection ? model.getValueInRange(selection) : '';
             const isSelectionEmpty = !selection || selection.isEmpty();

-            onRunQuery({
+            runQuery({
               value,
               selectedValue,
               isSelectionEmpty,
             });
           }
         });
-      }

       // Cleanup on dispose
       editor.onDidDispose(() => {
+        keydownDisposable.dispose();
         modelChangeDisposable?.dispose?.();
         const model = modelRef.current;
         if (model) sqlCompletionContextByModel.delete(model);
       });

Also remove onRunQuery from the handleEditorDidMount dependency array since the ref handles updates:

     [
       connector,
       customKeywords,
       customFunctions,
       getLatestSchemas,
       onMount,
-      onRunQuery,
       tableSchemas,
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/SqlMonacoEditor.tsx` around lines 361 - 382, The
keyboard handler registered via editor.onKeyDown closes over the stale
onRunQuery prop; update the handler to call onRunQueryRef.current(...) instead
of onRunQuery, and remove onRunQuery from the handleEditorDidMount dependency
array so updates are driven by the ref; also capture the disposable returned by
editor.onKeyDown (in handleEditorDidMount) and ensure you dispose it when
unmounting or before re-registering to avoid duplicate listeners.

Comment on lines +83 to +129
export function findTableAndColumn(
qualifiedName: ParsedQualifiedColumnName,
tables: DataTable[],
): {table: DataTable; column: TableColumn} | undefined {
const {columnName, tableName, schemaName, databaseName} = qualifiedName;

for (const table of tables) {
// Check if table matches the qualification (case-insensitive)
if (
databaseName &&
table.table.database?.localeCompare(databaseName, undefined, {
sensitivity: 'accent',
}) !== 0
) {
continue;
}
if (
schemaName &&
table.table.schema?.localeCompare(schemaName, undefined, {
sensitivity: 'accent',
}) !== 0
) {
continue;
}
if (
tableName &&
table.table.table?.localeCompare(tableName, undefined, {
sensitivity: 'accent',
}) !== 0
) {
continue;
}

// Find the column (case-insensitive)
const column = table.columns.find(
(col) =>
col.name?.localeCompare(columnName, undefined, {
sensitivity: 'accent',
}) === 0,
);
if (column) {
return {table, column};
}
}

return undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Ambiguous column lookup when only columnName is provided.

When a bare column name is passed (e.g., "id" without table qualification), tableName, schemaName, and databaseName will all be undefined. The conditions at lines 92, 100, and 108 will all be false (due to databaseName &&, schemaName &&, tableName &&), causing the function to skip all filtering and return the first table that has a matching column name.

This may cause unexpected behavior when multiple tables have identically named columns. Consider either:

  1. Documenting this as intentional "first match wins" behavior
  2. Returning all matches and letting the caller disambiguate
  3. Returning undefined when the result would be ambiguous
📖 Example of current behavior
// Given tables: users (id, name), orders (id, amount)
const result = findTableAndColumn({columnName: 'id'}, tables);
// Returns {table: users, column: id} - always first table with matching column
// This may not be what the caller expects
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/utils/qualified-name-parser.ts` around lines 83 -
129, findTableAndColumn currently treats a bare columnName as "first match
wins", causing ambiguous results when multiple tables contain the same column;
change the logic in findTableAndColumn to collect all matching {table, column}
pairs (searching tables by columnName using the existing localeCompare logic)
and if more than one match is found when tableName/schemaName/databaseName are
all undefined, return undefined (only return the single match if matches.length
=== 1). Use the existing symbols DataTable, TableColumn, columnName, tableName,
schemaName, databaseName and the function findTableAndColumn to locate where to
replace the one-pass return with the collection-and-disambiguation approach.

@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: 2

🧹 Nitpick comments (3)
packages/vega/src/editor/VegaSpecEditorPanel.tsx (1)

19-32: Update documentation to reflect CodeMirror migration.

The JSDoc comment at line 21 still references "Monaco editor" but the implementation now uses CodeMirror.

📝 Proposed documentation fix
 /**
  * Spec editor panel subcomponent for VegaLiteChart.Container.
- * Renders a Monaco editor with Vega-Lite JSON schema validation.
+ * Renders a CodeMirror editor with Vega-Lite JSON schema validation.
  *
  * Must be used within a VegaLiteChart.Container component.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vega/src/editor/VegaSpecEditorPanel.tsx` around lines 19 - 32, The
JSDoc for VegaSpecEditorPanel incorrectly mentions "Monaco editor"; update the
comment to reflect the CodeMirror migration by replacing references to "Monaco
editor" with "CodeMirror editor" and adjust any wording that implies
Monaco-specific behavior; update the example reference if it mentions
editor-specific props—target the VegaSpecEditorPanel component and the
VegaLiteChart.SpecEditor mention in the comment.
packages/vega/src/editor/VegaCodeMirrorEditor.tsx (1)

33-42: Consider adding cleanup for the async effect.

If the component unmounts while the schema is loading, setSchema could be called on an unmounted component. While React 18+ handles this more gracefully, adding a cleanup flag is a defensive best practice.

♻️ Proposed fix with cleanup
   // Load Vega-Lite schema on mount
   useEffect(() => {
+    let cancelled = false;
     if (enableSchemaValidation) {
       loadVegaLiteSchema().then((loadedSchema) => {
-        if (loadedSchema) {
+        if (loadedSchema && !cancelled) {
           setSchema(loadedSchema);
         }
       });
     }
+    return () => {
+      cancelled = true;
+    };
   }, [enableSchemaValidation]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vega/src/editor/VegaCodeMirrorEditor.tsx` around lines 33 - 42, The
effect in VegaCodeMirrorEditor that calls loadVegaLiteSchema and then setSchema
can call setState after unmount; update the useEffect to add a mounted/aborted
flag (e.g., let isMounted = true) before awaiting loadVegaLiteSchema, check the
flag before calling setSchema, and set the flag to false in the cleanup function
returned by useEffect; reference the useEffect block that calls
loadVegaLiteSchema and the setSchema state setter when applying the change.
packages/codemirror/src/utils/validate-json-schema.ts (1)

53-55: SyntaxError position extraction relies on V8-specific message format.

The regex /position (\d+)/ works for V8/Node but may not match error messages from other JavaScript engines (e.g., SpiderMonkey, JavaScriptCore), causing a silent fallback to position 0.

Consider this a low-risk edge case since most target environments use V8-based engines (Chrome, Node, Edge), but be aware the fallback may provide less precise error locations in other runtimes.

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

In `@packages/codemirror/src/utils/validate-json-schema.ts` around lines 53 - 55,
The current extraction of the JSON parse error position relies solely on the
V8-specific regex /position (\d+)/ using error.message, which can fail on other
engines; update the logic around error, error.message, errorMatch and position
to try multiple sources and patterns: first check structured fields like (error
as any).loc?.start?.column, (error as any).lineNumber or (error as
any).columnNumber, then try several regexes (e.g., /position (\d+)/, /at
position (\d+)/i, /char (\d+)/, /column (\d+)/i) against error.message and
parseInt the match, and only then fall back to 0; ensure you update the code
that sets position (the position constant) to use this prioritized extraction
sequence so non‑V8 engines get more accurate locations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/codemirror/src/utils/validate-json-schema.ts`:
- Around line 5-10: Update the JSDoc for the validateJsonSchema function to
reflect the current signature: replace the outdated `@param` schema entry with
`@param` validate - the precompiled ValidateFunction (from ajv) used to validate
the JSON text; keep the `@param` text description and the `@returns` description,
and ensure types and wording mention that validation uses the provided
ValidateFunction rather than a schema object. Reference the function name
validateJsonSchema and the parameter validate: ValidateFunction when making the
change.
- Around line 142-149: The pathSegments construction is not decoding JSON
Pointer escape sequences (~0 and ~1) per RFC 6901, so properties with '/' or '~'
in their names won't be resolved; update the mapping for error.instancePath to
first replace "~1" with "/" and "~0" with "~" on each segment (before numeric
conversion), then parseInt and convert numeric strings to numbers as currently
done; reference the error.instancePath split/map block (pathSegments) and ensure
the replace order is "~1" then "~0" to correctly decode pointers.

---

Nitpick comments:
In `@packages/codemirror/src/utils/validate-json-schema.ts`:
- Around line 53-55: The current extraction of the JSON parse error position
relies solely on the V8-specific regex /position (\d+)/ using error.message,
which can fail on other engines; update the logic around error, error.message,
errorMatch and position to try multiple sources and patterns: first check
structured fields like (error as any).loc?.start?.column, (error as
any).lineNumber or (error as any).columnNumber, then try several regexes (e.g.,
/position (\d+)/, /at position (\d+)/i, /char (\d+)/, /column (\d+)/i) against
error.message and parseInt the match, and only then fall back to 0; ensure you
update the code that sets position (the position constant) to use this
prioritized extraction sequence so non‑V8 engines get more accurate locations.

In `@packages/vega/src/editor/VegaCodeMirrorEditor.tsx`:
- Around line 33-42: The effect in VegaCodeMirrorEditor that calls
loadVegaLiteSchema and then setSchema can call setState after unmount; update
the useEffect to add a mounted/aborted flag (e.g., let isMounted = true) before
awaiting loadVegaLiteSchema, check the flag before calling setSchema, and set
the flag to false in the cleanup function returned by useEffect; reference the
useEffect block that calls loadVegaLiteSchema and the setSchema state setter
when applying the change.

In `@packages/vega/src/editor/VegaSpecEditorPanel.tsx`:
- Around line 19-32: The JSDoc for VegaSpecEditorPanel incorrectly mentions
"Monaco editor"; update the comment to reflect the CodeMirror migration by
replacing references to "Monaco editor" with "CodeMirror editor" and adjust any
wording that implies Monaco-specific behavior; update the example reference if
it mentions editor-specific props—target the VegaSpecEditorPanel component and
the VegaLiteChart.SpecEditor mention in the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82f7c28f-0395-4c4d-984e-40a4c62b5062

📥 Commits

Reviewing files that changed from the base of the PR and between a4e4984 and a1b9e23.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • packages/codemirror/__tests__/validate-json-schema.test.ts
  • packages/codemirror/src/components/JsonCodeMirrorEditor.tsx
  • packages/codemirror/src/extensions/json-schema-lint.ts
  • packages/codemirror/src/themes/tooltip-theme.ts
  • packages/codemirror/src/utils/create-json-schema-validator.ts
  • packages/codemirror/src/utils/validate-json-schema.ts
  • packages/vega/package.json
  • packages/vega/src/editor/VegaCodeMirrorEditor.tsx
  • packages/vega/src/editor/VegaMonacoEditor.tsx
  • packages/vega/src/editor/VegaSpecEditorPanel.tsx
  • packages/vega/src/editor/VegaSqlEditorPanel.tsx
  • packages/vega/src/index.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/vega/src/editor/VegaMonacoEditor.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/codemirror/src/utils/create-json-schema-validator.ts
  • packages/vega/src/editor/VegaSqlEditorPanel.tsx
  • packages/codemirror/src/extensions/json-schema-lint.ts
  • packages/codemirror/src/components/JsonCodeMirrorEditor.tsx
  • packages/codemirror/tests/validate-json-schema.test.ts

Comment on lines +5 to +10
/**
* Validates JSON text against a JSON schema and returns diagnostics
* @param text - JSON text to validate
* @param schema - JSON schema to validate against
* @returns Array of validation diagnostics
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

JSDoc @param schema is outdated; the parameter is now validate: ValidateFunction.

The documentation doesn't match the function signature after the refactor to accept a pre-compiled validator.

📝 Proposed fix
 /**
  * Validates JSON text against a JSON schema and returns diagnostics
  * `@param` text - JSON text to validate
- * `@param` schema - JSON schema to validate against
+ * `@param` validate - Pre-compiled Ajv validator function
  * `@returns` Array of validation diagnostics
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Validates JSON text against a JSON schema and returns diagnostics
* @param text - JSON text to validate
* @param schema - JSON schema to validate against
* @returns Array of validation diagnostics
*/
/**
* Validates JSON text against a JSON schema and returns diagnostics
* `@param` text - JSON text to validate
* `@param` validate - Pre-compiled Ajv validator function
* `@returns` Array of validation diagnostics
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/utils/validate-json-schema.ts` around lines 5 - 10,
Update the JSDoc for the validateJsonSchema function to reflect the current
signature: replace the outdated `@param` schema entry with `@param` validate - the
precompiled ValidateFunction (from ajv) used to validate the JSON text; keep the
`@param` text description and the `@returns` description, and ensure types and
wording mention that validation uses the provided ValidateFunction rather than a
schema object. Reference the function name validateJsonSchema and the parameter
validate: ValidateFunction when making the change.

Comment on lines +142 to +149
const pathSegments = error.instancePath
.split('/')
.filter((segment) => segment !== '')
.map((segment) => {
// Convert numeric strings to numbers for array indices
const num = parseInt(segment, 10);
return isNaN(num) ? segment : num;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

JSON Pointer escape sequences (~0, ~1) are not decoded.

While numeric array indices are now correctly converted, the path segments still need decoding per RFC 6901: ~1 → / and ~0 → ~. Properties containing / or ~ in their names will fail to resolve, causing fallback to document-level highlighting.

🔧 Proposed fix to handle JSON Pointer escapes
   const pathSegments = error.instancePath
     .split('/')
     .filter((segment) => segment !== '')
     .map((segment) => {
+      // Decode JSON Pointer escapes per RFC 6901
+      const decoded = segment.replace(/~1/g, '/').replace(/~0/g, '~');
       // Convert numeric strings to numbers for array indices
-      const num = parseInt(segment, 10);
-      return isNaN(num) ? segment : num;
+      const num = parseInt(decoded, 10);
+      return isNaN(num) ? decoded : num;
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pathSegments = error.instancePath
.split('/')
.filter((segment) => segment !== '')
.map((segment) => {
// Convert numeric strings to numbers for array indices
const num = parseInt(segment, 10);
return isNaN(num) ? segment : num;
});
const pathSegments = error.instancePath
.split('/')
.filter((segment) => segment !== '')
.map((segment) => {
// Decode JSON Pointer escapes per RFC 6901
const decoded = segment.replace(/~1/g, '/').replace(/~0/g, '~');
// Convert numeric strings to numbers for array indices
const num = parseInt(decoded, 10);
return isNaN(num) ? decoded : num;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemirror/src/utils/validate-json-schema.ts` around lines 142 -
149, The pathSegments construction is not decoding JSON Pointer escape sequences
(~0 and ~1) per RFC 6901, so properties with '/' or '~' in their names won't be
resolved; update the mapping for error.instancePath to first replace "~1" with
"/" and "~0" with "~" on each segment (before numeric conversion), then parseInt
and convert numeric strings to numbers as currently done; reference the
error.instancePath split/map block (pathSegments) and ensure the replace order
is "~1" then "~0" to correctly decode pointers.

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.

4 participants