Repository navigation
feat: Webcontainer package - #374
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughExtracts WebContainer logic into a new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant AppBuilder as AppBuilderSheet
participant Generator as AppGenerator
participant Store as RoomStore(WebContainerSlice)
participant WC as WebContainerRuntime
User->>AppBuilder: Create App (prompt + template)
AppBuilder->>Generator: generateAppFromPromptLocal(prompt, template)
Generator-->>AppBuilder: generated files (path→content)
AppBuilder->>AppBuilder: ensureRunnableViteScaffold(files)
AppBuilder->>Store: applyFilesTree(fileSystemTree)
Store->>WC: mountFilesTree()/initialize()
Store->>WC: startDevServer()
WC-->>Store: server ready (url)
Store-->>AppBuilder: serverStatus updated
AppBuilder->>WC: open BrowserView (preview iframe)
User->>AppBuilder: edit file
AppBuilder->>Store: updateFileContent(path, content)
Store->>WC: sync file to virtual FS
WC-->>User: HMR / preview updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts (1)
1-4: Same deep-import concern as increateListFilesTool.Consider importing
WebContainerSliceStatefrom@sqlrooms/webcontainerto avoid coupling to the build output path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts` around lines 1 - 4, The import of WebContainerSliceState uses a deep build-output path which couples code to the dist layout; replace the deep import in updateFileContentTool.ts by importing WebContainerSliceState from the package entry (e.g., '@sqlrooms/webcontainer') instead of '../../../../../packages/webcontainer/dist' and update any references in the file (WebContainerSliceState) accordingly so the module resolution uses the package boundary rather than the build output path.examples/app-builder/src/tools/getFileContent/getFileContentTool.ts (1)
2-4: Same deep-import concern as in the other tool files.Consider importing
WebContainerSliceStatefrom@sqlrooms/webcontainerto avoid coupling to the build output path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/app-builder/src/tools/getFileContent/getFileContentTool.ts` around lines 2 - 4, The import of the type WebContainerSliceState is using a deep build-output path which couples code to the repo layout; replace the deep import with a package-level type import (e.g. change import type { WebContainerSliceState } from '../../../../../packages/webcontainer/dist' to import type { WebContainerSliceState } from '@sqlrooms/webcontainer') so the file (getFileContentTool.ts) depends on the published package API rather than the build output.
🧹 Nitpick comments (1)
examples/app-builder/src/tools/listFiles/createListFilesTool.ts (1)
1-5: Prefer public package entry over deepdistimport.Importing from
packages/webcontainer/distcouples to build output and can be brittle in dev. Prefer the package export.♻️ Proposed change
-import type {WebContainerSliceState} from '../../../../../packages/webcontainer/dist'; +import type {WebContainerSliceState} from '@sqlrooms/webcontainer';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/app-builder/src/tools/listFiles/createListFilesTool.ts` around lines 1 - 5, The file currently imports the type WebContainerSliceState via a deep build output path ('../../../../../packages/webcontainer/dist'); replace that with an import from the package's public entry (e.g. import type { WebContainerSliceState } from 'webcontainer' or the package's actual npm scope name) to avoid coupling to dist output—update the import in createListFilesTool.ts to reference the package's exported entry and ensure WebContainerSliceState is exported there (adjust the package name to the correct public package if different).
🤖 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/webcontainer/src/WebContainerSlice.ts`:
- Around line 121-126: The call to get().webcontainer.startDevServer() is
fire-and-forget and can leave initialization appearing successful when it
actually fails; change it to await the startDevServer() promise and handle
rejections (e.g., wrap await get().webcontainer.startDevServer() in try/catch),
logging or rethrowing a descriptive Error when it rejects so initialization
surfaces the failure (keep the existing installDependencies() check using
get().webcontainer.installDependencies() and ensure both errors are handled
consistently).
- Around line 86-100: When reusing the cached WebContainer instance the code
always opens the hardcoded '/src/App.jsx'; change it to honor the configured
active file by reading the current active file path from state (e.g.
get().webcontainer.activeFilePath or get().activeFilePath) and pass that into
webcontainer.openFile instead of the literal '/src/App.jsx', falling back to
'/src/App.jsx' only if the configured path is empty/undefined.
- Around line 140-149: The nested update to state.webcontainer inside the
installProcess.output WritableStream should use Immer's produce to preserve
immutable updates; replace the set callback that directly spreads
state.webcontainer and appends data with a set call that uses produce(state,
draft => { draft.webcontainer.output += data; }) (referencing installProcess,
instance.spawn, installProcess.output, set, and webcontainer.output) so the
update matches other usages of produce in this slice.
---
Duplicate comments:
In `@examples/app-builder/src/tools/getFileContent/getFileContentTool.ts`:
- Around line 2-4: The import of the type WebContainerSliceState is using a deep
build-output path which couples code to the repo layout; replace the deep import
with a package-level type import (e.g. change import type {
WebContainerSliceState } from '../../../../../packages/webcontainer/dist' to
import type { WebContainerSliceState } from '@sqlrooms/webcontainer') so the
file (getFileContentTool.ts) depends on the published package API rather than
the build output.
In `@examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts`:
- Around line 1-4: The import of WebContainerSliceState uses a deep build-output
path which couples code to the dist layout; replace the deep import in
updateFileContentTool.ts by importing WebContainerSliceState from the package
entry (e.g., '@sqlrooms/webcontainer') instead of
'../../../../../packages/webcontainer/dist' and update any references in the
file (WebContainerSliceState) accordingly so the module resolution uses the
package boundary rather than the build output path.
---
Nitpick comments:
In `@examples/app-builder/src/tools/listFiles/createListFilesTool.ts`:
- Around line 1-5: The file currently imports the type WebContainerSliceState
via a deep build output path ('../../../../../packages/webcontainer/dist');
replace that with an import from the package's public entry (e.g. import type {
WebContainerSliceState } from 'webcontainer' or the package's actual npm scope
name) to avoid coupling to dist output—update the import in
createListFilesTool.ts to reference the package's exported entry and ensure
WebContainerSliceState is exported there (adjust the package name to the correct
public package if different).
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (23)
apps/sqlrooms-cli-ui/src/store.tsexamples/app-builder/README.mdexamples/app-builder/package.jsonexamples/app-builder/src/components/BrowserView.tsxexamples/app-builder/src/components/CodeView.tsxexamples/app-builder/src/components/TerminalView.tsxexamples/app-builder/src/components/filetree/FileTreeView.tsxexamples/app-builder/src/store/WebContainerSlice.tsexamples/app-builder/src/store/store.tsexamples/app-builder/src/tools/getFileContent/getFileContentTool.tsexamples/app-builder/src/tools/listFiles/createListFilesTool.tsexamples/app-builder/src/tools/updateFileContent/updateFileContentTool.tsexamples/canvas/src/store.tspackages/cells/src/execution.tspackages/webcontainer/README.mdpackages/webcontainer/eslint.config.jspackages/webcontainer/package.jsonpackages/webcontainer/src/WebContainerSlice.tspackages/webcontainer/src/index.tspackages/webcontainer/src/setFileContentInTree.tspackages/webcontainer/src/webContainerCache.tspackages/webcontainer/tsconfig.jsonpackages/webcontainer/typedoc.js
💤 Files with no reviewable changes (1)
- examples/app-builder/src/store/WebContainerSlice.ts
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
4c70ec5 to
b2342c8
Compare
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/webcontainer/src/setFileContentInTree.ts (1)
6-8:⚠️ Potential issue | 🟡 MinorRegex matches backslashes instead of forward slashes.
The regex
/\\+/gmatches one or more backslashes (\), not forward slashes. To collapse multiple forward slashes, use/\/+/g.Proposed fix
export function normalizePath(path: string): string { - return (path.startsWith('/') ? path.slice(1) : path).replace(/\\+/g, '/'); + return (path.startsWith('/') ? path.slice(1) : path).replace(/\/+/g, '/'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/setFileContentInTree.ts` around lines 6 - 8, The normalizePath function currently uses the regex /\\+/g which matches backslashes; update the regex in normalizePath to collapse consecutive forward slashes by using /\/+/g instead so inputs like "//foo///bar" become "foo/bar" (keep the existing leading slash strip via path.startsWith('/') and the replace call on the normalized string).
🧹 Nitpick comments (5)
examples/app-builder/src/tools/listFiles/createListFilesTool.ts (1)
37-42: Reuse the existing schema constant to avoid duplication.The
parametersschema here is identical to the module-levelListFilesToolParametersdefined on lines 12-17. Reusing the constant avoids duplication and ensures consistency.♻️ Proposed fix
return { name: 'listFiles', description: 'List project files', - parameters: z.object({ - basePath: z - .string() - .describe('Optional base path to list files from') - .default('/'), - }), + parameters: ListFilesToolParameters, execute: async ({basePath = '/'}: ListFilesToolParameters) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/app-builder/src/tools/listFiles/createListFilesTool.ts` around lines 37 - 42, Replace the inline parameters schema with the existing module-level constant to avoid duplication: use the ListFilesToolParameters constant in place of the inline parameters object in createListFilesTool (replace the current "parameters: z.object({...})" block with a reference to ListFilesToolParameters) so both the tool and the module share the same schema definition.packages/webcontainer/src/setFileContentInTree.ts (1)
42-44: Defensive guard is reasonable but appears unreachable.Given that
pathToSegmentsfilters out empty segments and line 65 returns early whensegments.length === 0, this guard should never trigger in normal execution. If it's intended as defensive coding against future refactoring, consider adding a comment explaining the intent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/setFileContentInTree.ts` around lines 42 - 44, The guard `if (!segment) return subtree;` inside setFileContentInTree is unreachable in normal execution because pathToSegments filters out empty segments and there's an early return when `segments.length === 0`; either remove the guard or (preferred) keep it but add a one-line comment above it explaining it's a defensive check against future refactors or unexpected inputs (mentioning that pathToSegments filters empties and segments.length === 0 is handled earlier) so readers know it's intentional rather than dead code.examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts (1)
35-37: Unnecessaryawaiton synchronous method.
updateFileContentis synchronous (returnsvoid), so theawaiton line 36 is unnecessary. However, it's harmless and may serve as defensive coding if the method becomes async in the future.♻️ Optional: Remove unnecessary await
execute: async ({path, content}: UpdateFileContentToolParameters) => { - await store.getState().webcontainer.updateFileContent(path, content); + store.getState().webcontainer.updateFileContent(path, content); await store.getState().webcontainer.saveAllOpenFiles();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts` around lines 35 - 37, The call to store.getState().webcontainer.updateFileContent in the execute function is synchronous (returns void) so the await before it is unnecessary; remove the redundant await in execute (leave the subsequent await on saveAllOpenFiles, which is async) by updating the execute implementation to call updateFileContent(path, content) without await while keeping await on saveAllOpenFiles() to preserve async behavior.packages/webcontainer/src/WebContainerSlice.ts (2)
208-240:startDevServerresolves before server is actually ready.The function returns (resolves) immediately after spawning
npm run devand setting state tostarting-dev. The actual "ready" state is set asynchronously in theserver-readycallback. Callers awaiting this function may incorrectly assume the server is ready when it's not.Consider returning a Promise that resolves only when
server-readyfires or an error occurs:♻️ Suggested approach
async startDevServer() { const instance = get().webcontainer.instance; if (!instance) { throw new Error('WebContainer instance not found'); } - // Run `npm run dev` to start the Vite dev server - await instance.spawn('npm', ['run', 'dev']); - set((state) => - produce(state, (draft) => { - draft.webcontainer.serverStatus = {type: 'starting-dev'}; - }), - ); - - // Wait for `server-ready` event - instance.on('server-ready', (port, url) => { - console.log(`Server ready on port ${port} at ${url}`); - setCachedWebContainerServerUrl(url); - set((state) => - produce(state, (draft) => { - draft.webcontainer.serverStatus = {type: 'ready', url}; - }), - ); - }); - - instance.on('error', (error) => { - console.error('Server error', error); - set((state) => - produce(state, (draft) => { - draft.webcontainer.serverStatus = {type: 'error', error}; - }), - ); - }); + return new Promise<void>((resolve, reject) => { + instance.on('server-ready', (port, url) => { + console.log(`Server ready on port ${port} at ${url}`); + setCachedWebContainerServerUrl(url); + set((state) => + produce(state, (draft) => { + draft.webcontainer.serverStatus = {type: 'ready', url}; + }), + ); + resolve(); + }); + + instance.on('error', (error) => { + console.error('Server error', error); + set((state) => + produce(state, (draft) => { + draft.webcontainer.serverStatus = {type: 'error', error}; + }), + ); + reject(error); + }); + + // Start the dev server after listeners are attached + instance.spawn('npm', ['run', 'dev']); + set((state) => + produce(state, (draft) => { + draft.webcontainer.serverStatus = {type: 'starting-dev'}; + }), + ); + }); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerSlice.ts` around lines 208 - 240, startDevServer currently spawns the dev server and returns immediately, so callers may think the server is ready while it isn’t; change startDevServer to return a Promise that only resolves when the instance emits 'server-ready' (and sets setCachedWebContainerServerUrl and draft.webcontainer.serverStatus to ready) and rejects if the instance emits 'error' (and sets serverStatus to error). Attach one-time listeners to instance.on('server-ready', ...) and instance.on('error', ...), resolve/reject the Promise inside those callbacks, and remove the listeners after they fire to avoid leaks; keep existing state updates (produce/set) in those callbacks so behavior is unchanged except for the Promise lifecycle.
347-357: UnreachableTextDecoderbranch when using'utf-8'encoding.When
readFileis called with'utf-8'encoding,@webcontainer/apireturns astringdirectly. TheUint8Arrayfallback on line 352 is unreachable.♻️ Simplify the type handling
async getFileContent(path) { const state = get(); const opened = state.webcontainer.config.openedFiles.find( (f) => f.path === path, ); if (opened) return opened.content; const instance = state.webcontainer.instance; try { if (instance) { - const data = await instance.fs.readFile(path, 'utf-8'); - return typeof data === 'string' - ? data - : new TextDecoder().decode(data as Uint8Array); + return await instance.fs.readFile(path, 'utf-8'); } } catch (_e) { // Swallow and return empty string } return ''; },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerSlice.ts` around lines 347 - 357, The code calls instance.fs.readFile(path, 'utf-8') and then checks for a Uint8Array fallback with new TextDecoder(), but `@webcontainer/api` returns a string when 'utf-8' is passed so the Uint8Array branch is unreachable; simplify by treating the result as a string (remove the typeof check and TextDecoder fallback) and return the data directly, or if you need binary support instead call instance.fs.readFile(path) without the 'utf-8' option and handle a Uint8Array there (adjust logic in the read helper around instance.fs.readFile and remove the TextDecoder path).
🤖 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/app-builder/src/components/TerminalView.tsx`:
- Line 66: The className on the container div in TerminalView.tsx uses "gap-21"
which is likely a typo and creates an unexpectedly large spacing; update the
className on the div rendered in the component (the <div className="flex h-full
flex-col gap-21 overflow-hidden">) to the intended spacing (e.g., "gap-2" or
another appropriate Tailwind gap utility) so the flex column spacing is correct.
---
Outside diff comments:
In `@packages/webcontainer/src/setFileContentInTree.ts`:
- Around line 6-8: The normalizePath function currently uses the regex /\\+/g
which matches backslashes; update the regex in normalizePath to collapse
consecutive forward slashes by using /\/+/g instead so inputs like "//foo///bar"
become "foo/bar" (keep the existing leading slash strip via path.startsWith('/')
and the replace call on the normalized string).
---
Nitpick comments:
In `@examples/app-builder/src/tools/listFiles/createListFilesTool.ts`:
- Around line 37-42: Replace the inline parameters schema with the existing
module-level constant to avoid duplication: use the ListFilesToolParameters
constant in place of the inline parameters object in createListFilesTool
(replace the current "parameters: z.object({...})" block with a reference to
ListFilesToolParameters) so both the tool and the module share the same schema
definition.
In `@examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts`:
- Around line 35-37: The call to store.getState().webcontainer.updateFileContent
in the execute function is synchronous (returns void) so the await before it is
unnecessary; remove the redundant await in execute (leave the subsequent await
on saveAllOpenFiles, which is async) by updating the execute implementation to
call updateFileContent(path, content) without await while keeping await on
saveAllOpenFiles() to preserve async behavior.
In `@packages/webcontainer/src/setFileContentInTree.ts`:
- Around line 42-44: The guard `if (!segment) return subtree;` inside
setFileContentInTree is unreachable in normal execution because pathToSegments
filters out empty segments and there's an early return when `segments.length ===
0`; either remove the guard or (preferred) keep it but add a one-line comment
above it explaining it's a defensive check against future refactors or
unexpected inputs (mentioning that pathToSegments filters empties and
segments.length === 0 is handled earlier) so readers know it's intentional
rather than dead code.
In `@packages/webcontainer/src/WebContainerSlice.ts`:
- Around line 208-240: startDevServer currently spawns the dev server and
returns immediately, so callers may think the server is ready while it isn’t;
change startDevServer to return a Promise that only resolves when the instance
emits 'server-ready' (and sets setCachedWebContainerServerUrl and
draft.webcontainer.serverStatus to ready) and rejects if the instance emits
'error' (and sets serverStatus to error). Attach one-time listeners to
instance.on('server-ready', ...) and instance.on('error', ...), resolve/reject
the Promise inside those callbacks, and remove the listeners after they fire to
avoid leaks; keep existing state updates (produce/set) in those callbacks so
behavior is unchanged except for the Promise lifecycle.
- Around line 347-357: The code calls instance.fs.readFile(path, 'utf-8') and
then checks for a Uint8Array fallback with new TextDecoder(), but
`@webcontainer/api` returns a string when 'utf-8' is passed so the Uint8Array
branch is unreachable; simplify by treating the result as a string (remove the
typeof check and TextDecoder fallback) and return the data directly, or if you
need binary support instead call instance.fs.readFile(path) without the 'utf-8'
option and handle a Uint8Array there (adjust logic in the read helper around
instance.fs.readFile and remove the TextDecoder path).
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (20)
examples/app-builder/README.mdexamples/app-builder/package.jsonexamples/app-builder/src/components/BrowserView.tsxexamples/app-builder/src/components/CodeView.tsxexamples/app-builder/src/components/TerminalView.tsxexamples/app-builder/src/components/filetree/FileTreeView.tsxexamples/app-builder/src/store/WebContainerSlice.tsexamples/app-builder/src/store/store.tsexamples/app-builder/src/tools/getFileContent/getFileContentTool.tsexamples/app-builder/src/tools/listFiles/createListFilesTool.tsexamples/app-builder/src/tools/updateFileContent/updateFileContentTool.tspackages/webcontainer/README.mdpackages/webcontainer/eslint.config.jspackages/webcontainer/package.jsonpackages/webcontainer/src/WebContainerSlice.tspackages/webcontainer/src/index.tspackages/webcontainer/src/setFileContentInTree.tspackages/webcontainer/src/webContainerCache.tspackages/webcontainer/tsconfig.jsonpackages/webcontainer/typedoc.js
💤 Files with no reviewable changes (1)
- examples/app-builder/src/store/WebContainerSlice.ts
| return ( | ||
| <div className="relative h-full w-full p-2"> | ||
| <div className="gap-21 flex h-full flex-col overflow-hidden"> | ||
| <div className="flex h-full flex-col gap-21 overflow-hidden"> |
There was a problem hiding this comment.
Possible typo: gap-21 seems unusually large.
gap-21 in Tailwind CSS equals 5.25rem (84px), which appears excessive for a flex container layout. Was this intended to be gap-2?
🐛 Suggested fix if this is a typo
- <div className="flex h-full flex-col gap-21 overflow-hidden">
+ <div className="flex h-full flex-col gap-2 overflow-hidden">📝 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.
| <div className="flex h-full flex-col gap-21 overflow-hidden"> | |
| <div className="flex h-full flex-col gap-2 overflow-hidden"> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/app-builder/src/components/TerminalView.tsx` at line 66, The
className on the container div in TerminalView.tsx uses "gap-21" which is likely
a typo and creates an unexpectedly large spacing; update the className on the
div rendered in the component (the <div className="flex h-full flex-col gap-21
overflow-hidden">) to the intended spacing (e.g., "gap-2" or another appropriate
Tailwind gap utility) so the flex column spacing is correct.
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (15)
.cursor/plans/sqlrooms_cli_app-builder_plan_(adjusted)_174b3a95.plan.md (1)
224-257: Add an explicit build prerequisite before example/UI validation steps.Given the milestone and success-criteria flow, add a short prerequisite note that package builds must run first; otherwise example app flows can fail due to unresolved
workspace:*links.Based on learnings: Run
pnpm buildbefore running any example app, as examples depend on builtsqlrooms/*packages viaworkspace:*links.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.cursor/plans/sqlrooms_cli_app-builder_plan_(adjusted)_174b3a95.plan.md around lines 224 - 257, Add an explicit build prerequisite sentence/note before the example/UI validation steps (e.g., right before "Milestone 1: Artifact MVP" or the Example/App run instructions) stating that packages must be built first and providing the exact command to run: "Run pnpm build before running any example app" to ensure workspace:* linked packages are resolved; reference the workspace linking issue by mentioning "workspace:* links" so readers know why the prerequisite is required.packages/webcontainer/src/setFileContentInTree.ts (1)
6-8: Edge case: paths starting with multiple slashes are not fully normalized.If the input starts with
//(e.g.,//a//b),slice(1)removes only one slash, leaving/a//b. After the replace, you get/a/b— still with a leading slash, which doesn't match the docstring's "removing a leading slash."Since
pathToSegmentsfilters empty strings after split, this bug doesn't affectsetFileContentInTree. However,normalizePathis exported and could be used directly elsewhere.Proposed fix to handle multiple leading slashes
export function normalizePath(path: string): string { - return (path.startsWith('/') ? path.slice(1) : path).replace(/\/+/g, '/'); + return path.replace(/^\/+/, '').replace(/\/+/g, '/'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/setFileContentInTree.ts` around lines 6 - 8, normalizePath currently removes only a single leading slash then collapses repeated slashes, so inputs like "//a//b" still end up with a leading slash; update normalizePath to strip all leading slashes (e.g., remove prefix matching one-or-more '/' characters) before collapsing consecutive slashes so the function always returns a path without any leading '/' as documented; reference normalizePath (and note callers like pathToSegments / setFileContentInTree rely on normalized segments) and ensure the new implementation is exported the same way.packages/webcontainer/src/components/CodeView.tsx (2)
23-25: Selector calls a method, which may defeat memoization.Calling
hasDirtyFiles()inside the selector could cause re-renders on every state change if this method performs computation. Consider selecting the underlyingopenedFilesstate and deriving the dirty check in the component:- const hasDirty = useStoreWithWebContainer((s) => - s.webContainer.hasDirtyFiles(), - ); + const hasDirty = useMemo( + () => openedFiles.some((f) => f.dirty), + [openedFiles], + );This leverages the already-selected
openedFilesand avoids potential memoization issues with method calls in selectors. As per coding guidelines: "Use Zustand selectors with memoization to avoid unnecessary re-renders."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/components/CodeView.tsx` around lines 23 - 25, The selector currently calls webContainer.hasDirtyFiles() via useStoreWithWebContainer which can defeat selector memoization; instead select the underlying state slice (e.g. webContainer.openedFiles or webContainer.openedFilesMap) using useStoreWithWebContainer and compute hasDirty locally in CodeView (replace hasDirty usage with a derived value computed from the selected openedFiles), updating references to hasDirty, useStoreWithWebContainer, and webContainer.hasDirtyFiles accordingly so the selector only returns raw state and not a method call.
77-86: Hardcoded language won't highlight non-JavaScript files correctly.The
language="javascript"prop means TypeScript, JSON, CSS, HTML, and other file types won't get proper syntax highlighting. Consider deriving the language from the file extension:♻️ Suggested approach
const getLanguageFromPath = (path: string): string => { const ext = path.split('.').pop()?.toLowerCase(); const langMap: Record<string, string> = { js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript', json: 'json', css: 'css', html: 'html', md: 'markdown', }; return langMap[ext ?? ''] ?? 'plaintext'; }; // In the component: <MonacoEditor className="h-full w-full" language={activeFilePath ? getLanguageFromPath(activeFilePath) : 'plaintext'} value={activeFile?.content ?? ''} // ... />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/components/CodeView.tsx` around lines 77 - 86, The MonacoEditor currently hardcodes language="javascript", so derive the editor language from the active file path: add a helper (e.g., getLanguageFromPath) that maps file extensions (js, jsx -> javascript; ts, tsx -> typescript; json -> json; css -> css; html -> html; md -> markdown; default -> plaintext), then pass language={activeFilePath ? getLanguageFromPath(activeFilePath) : 'plaintext'} to the MonacoEditor; update references to MonacoEditor, activeFilePath, activeFile, and updateFileContent accordingly so other behavior is unchanged.apps/sqlrooms-cli-ui/plugins/scaffolds.ts (2)
21-23: Missing error handling could crash dev server or fail build silently.If
generateScaffoldsModulethrows (e.g., file read error), the error propagates unhandled. Consider wrapping in try-catch:🛡️ Proposed fix
async buildStart() { - await runGenerate(); + try { + await runGenerate(); + } catch (err) { + console.error('[scaffolds plugin] Failed to generate scaffolds:', err); + throw err; // Re-throw to fail the build + } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/plugins/scaffolds.ts` around lines 21 - 23, Wrap the await runGenerate() call inside buildStart() with a try-catch to prevent unhandled exceptions from generateScaffoldsModule/runGenerate from crashing the dev server or failing silently; in the catch block, log the error (use this.error(err) when available in the plugin context, otherwise console.error) and rethrow or call this.error to surface the failure to the build system so the error is visible.
27-34: Rapid file changes may cause concurrent generation races.Multiple rapid file system events (e.g., saving multiple files, IDE auto-formatting) will trigger concurrent
runGenerate()calls without debouncing. Consider adding a simple debounce:♻️ Optional debounce pattern
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null; + return { name: 'sqlrooms-cli-scaffolds', async buildStart() { await runGenerate(); }, configureServer(server) { const watcher = server.watcher; watcher.add(scaffoldsRoot); const onFsEvent = async (changedPath: string) => { if (!changedPath.startsWith(scaffoldsRoot)) return; if (changedPath === outputFile) return; - await runGenerate(); + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(async () => { + try { + await runGenerate(); + } catch (err) { + console.error('[scaffolds plugin] Generation failed:', err); + } + }, 100); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/plugins/scaffolds.ts` around lines 27 - 34, The onFsEvent handler currently calls runGenerate() for every matching file event which can cause concurrent runs; modify the watcher callbacks (watcher.on('add'/'change'/'unlink') -> onFsEvent) to debounce/coalesce rapid events by adding a short timer (e.g., 100–300ms) and clearing previous timers on new events, and ensure you avoid overlapping executions by using a running flag or awaiting the single scheduled run; update the symbols onFsEvent and runGenerate to use a shared debounceTimer and optionally an isRunning flag so multiple events within the debounce window only schedule one runGenerate and concurrent runs are prevented while respecting scaffoldsRoot and outputFile checks.apps/sqlrooms-cli-ui/plugins/generateScaffolds.ts (1)
73-76: Binary files will be corrupted when read as UTF-8.Reading all files with
'utf-8'encoding will corrupt binary files (images, fonts, etc.). Consider either:
- Filtering out binary files by extension
- Detecting binary content and using base64 encoding
- Documenting that scaffolds should only contain text files
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/plugins/generateScaffolds.ts` around lines 73 - 76, The current code in generateScaffolds.ts always calls fs.readFile(abs, 'utf-8') and writes to result[entry.name].file.contents, which will corrupt binary files; change the logic that builds result[entry.name] so that you detect whether a file is binary (e.g., by checking extension or probing contents after reading as a Buffer) and branch: for text files keep reading as UTF-8 and set file.contents to the string, for binary files read as a Buffer and store base64-encoded contents plus an explicit encoding flag (e.g., file.encoding = 'base64') so consumers can decode; update the code paths that consume result[...].file to respect the new encoding field.packages/webcontainer/src/WebContainerSlice.ts (1)
550-556: Useproduce()for nested state update consistency.This state update uses object spread instead of
produce(), which is inconsistent with the rest of the file. Per coding guidelines, useproduce()from Immer when updating nested state in Zustand store.♻️ Proposed fix
- set((state) => ({ - webContainer: { - ...state.webContainer, - output: state.webContainer.output + text, - }, - })); + set((state) => + produce(state, (draft) => { + draft.webContainer.output += text; + }), + );Based on learnings: "Always use
produce()from Immer when updating nested state in Zustand store to ensure immutable updates."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerSlice.ts` around lines 550 - 556, The nested Zustand state update for webContainer.output in the setter should use produce() from Immer instead of object spread to remain consistent and safe; locate the set call that currently does set((state) => ({ webContainer: { ...state.webContainer, output: state.webContainer.output + text } })), replace it with a produce-based update that mutates draft.webContainer.output (appending text) inside produce, ensuring you import/ use the existing produce symbol and keep other webContainer fields untouched.packages/webcontainer/src/components/TerminalView.tsx (1)
18-42: Potential unnecessary terminal recreation whenoutputchanges.The first
useEffecthasoutputin its dependency array, but the cleanup disposes the terminal and setsterminalRef.current = null. Whenoutputchanges, this causes the terminal to be destroyed and recreated, which is inefficient since the seconduseEffectalready handles incremental output updates.Consider removing
outputfrom the dependency array and relying solely on the second effect for output synchronization:♻️ Proposed fix
useEffect(() => { if (!containerRef.current || terminalRef.current) { return; } const term = new Terminal({ convertEol: true, disableStdin: true, fontSize: 12, cursorBlink: false, scrollback: 5000, }); term.open(containerRef.current); terminalRef.current = term; - if (output && output.length > 0) { - term.write(output); - lastIndexRef.current = output.length; - } - return () => { term.dispose(); terminalRef.current = null; lastIndexRef.current = 0; }; - }, [output]); + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/components/TerminalView.tsx` around lines 18 - 42, The effect that creates and disposes the xterm instance (uses Terminal, containerRef, terminalRef, lastIndexRef) should only run once on mount/unmount — remove output from the dependency array so it becomes [] — so the terminal is not destroyed/recreated whenever output changes; keep the existing cleanup (term.dispose(), terminalRef.current = null, lastIndexRef.current = 0) and rely on the other useEffect that incrementally writes new output to synchronize terminal contents.apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx (3)
183-183: Consider using a proper type instead ofany.Using
anyfor therootobject loses type safety. A recursive type orRecord<string, unknown>would be safer.Suggested change
function toFileSystemTree( files: Array<{path: string; content: string}>, ): FileSystemTree { - const root: any = {}; + const root: Record<string, any> = {};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` at line 183, The declaration "const root: any = {}" in AppBuilderSheet.tsx loses type safety; replace the any with a precise type such as Record<string, unknown> or define a recursive interface (e.g., type Node = { [key: string]: Node | unknown }) and use that for the root variable so consumers of root (and functions interacting with it) get proper type checks; locate the root binding in AppBuilderSheet.tsx and update its type, adjusting any downstream code with narrow typings or type guards as needed.
60-68: Potential redundant effect triggers due toappState?.filesin dependencies.Including
appState?.filesas a dependency alongsideappState?.updatedAtmay cause redundant triggers since both change together. Additionally, ifloadArtifactRuntimemodifies files viaupdateSheetAppFiles(line 51), this could trigger re-runs, though thepatched.length > 0guard should prevent infinite loops.Consider relying solely on
appState?.updatedAtas the change indicator:Suggested change
React.useEffect(() => { if (!currentSheetId || !appState?.files) return; void loadArtifactRuntime(currentSheetId, appState.files); }, [ currentSheetId, appState?.updatedAt, - appState?.files, loadArtifactRuntime, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` around lines 60 - 68, The effect dependencies include appState?.files which can redundantly retrigger the effect alongside appState?.updatedAt and may re-run when loadArtifactRuntime calls updateSheetAppFiles (even if patched.length > 0 prevents loops); remove appState?.files from the dependency array and depend only on currentSheetId and appState?.updatedAt and a stable loadArtifactRuntime (ensure loadArtifactRuntime is memoized with useCallback or otherwise stable) so the effect only runs when the sheet or its update timestamp changes.
293-325: Retry loop never actually retries different logic.The
while (attempts < 3)loop always produces the same output on each iteration sinceensureRunnableViteScaffolddeterministically adds all required files. Themissingarray will always be empty after the first call, making the retry mechanism ineffective.If this is placeholder code for future AI-based generation that might fail, consider adding a comment to clarify intent. Otherwise, the loop can be simplified.
Suggested simplification if retry is not needed
function generateAppFromPromptLocal(input: { prompt: string; template: string; }): { status: 'ok' | 'error'; attempts: number; errors: string[]; files: Array<{path: string; content: string}>; } { - const errors: string[] = []; - let attempts = 0; - let files: Array<{path: string; content: string}> = []; - while (attempts < 3) { - attempts += 1; - const appTitle = - input.template === 'basic-dashboard' - ? 'Analytics App' - : 'Mosaic Analytics App'; - const appFile = `import React from 'react'; + const appTitle = + input.template === 'basic-dashboard' + ? 'Analytics App' + : 'Mosaic Analytics App'; + const appFile = `import React from 'react'; export default function App() { return ( <main style={{padding: 16}}> <h1>${appTitle}</h1> <p>Prompt: ${input.prompt.replace(/</g, '<')}</p> <p>Template: ${input.template}</p> </main> ); } `; - files = ensureRunnableViteScaffold([ - {path: '/src/App.jsx', content: appFile}, - ]).files; - const required = new Set(files.map((f) => f.path)); - const missing = [ - '/index.html', - '/src/main.jsx', - '/src/App.jsx', - '/package.json', - ].filter((p) => !required.has(p)); - if (missing.length === 0) { - return {status: 'ok', attempts, errors, files}; - } - errors.push(...missing.map((m) => `missing required file: ${m}`)); - } - return {status: 'error', attempts, errors, files}; + const files = ensureRunnableViteScaffold([ + {path: '/src/App.jsx', content: appFile}, + ]).files; + return {status: 'ok', attempts: 1, errors: [], files}; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` around lines 293 - 325, The retry while loop in AppBuilderSheet.tsx around ensureRunnableViteScaffold is ineffective because ensureRunnableViteScaffold deterministically supplies the required files so `missing` will be empty after the first call; remove the loop and call ensureRunnableViteScaffold once (or if you intend future non-deterministic generation, add a clear TODO comment and break/return after the first failure), then compute `required`, `missing`, and return {status: 'ok'...} or push errors accordingly; specifically edit the block that uses attempts, ensureRunnableViteScaffold, required/missing and errors so it performs a single attempt (or documents retry intent) instead of looping.apps/sqlrooms-cli-ui/src/store.ts (1)
248-248: Type assertion onscaffoldsmay fail silently.Casting
scaffoldstoRecord<string, any>bypasses type checking. If the JSON structure changes, this could cause runtime errors.Consider adding validation or a more specific type
- filesTree: (scaffolds as Record<string, any>)['get-started'] ?? {}, + filesTree: scaffolds['get-started'] ?? {},If the import typing from the JSON is incorrect, consider defining a proper type or using Zod to validate the structure at runtime.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/src/store.ts` at line 248, The cast of scaffolds to Record<string, any> for initializing filesTree is unsafe; update the code around filesTree and scaffolds (the import/variable named scaffolds and the property 'get-started') to use a proper typed interface or runtime validation: define a specific type (e.g., ScaffoldsMap with a 'get-started' entry shape) or add a Zod schema to validate scaffolds after import, then use the validated value (or a safe fallback {}) when assigning filesTree so you don't bypass type checks and avoid silent runtime failures.python/sqlrooms-cli/tests/test_cli.py (1)
11-14: Use a behavior-level CLI assertion instead of only checking registration.This test can pass even if the command is broken at invocation time. Prefer invoking
export --help(and consider renaming the test fromtest_cli_versionto match intent).Proposed refactor
-def test_cli_version(): - # `--version` is not configured; assert expected command registration instead. - command_names = [command.name for command in app.registered_commands] - assert "export" in command_names +def test_cli_export_command_registered(): + result = runner.invoke(app, ["export", "--help"]) + assert result.exit_code == 0 + assert "Output directory for exported artifacts." in result.stdout🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/tests/test_cli.py` around lines 11 - 14, The test test_cli_version only checks app.registered_commands which can miss invocation failures; update it to actually invoke the CLI for the "export" command (e.g., use click.testing.CliRunner to run "export --help"), assert the invocation exit_code is 0 and that the output contains expected help text (like "Usage" or "export"), and rename the test to something like test_cli_export_help to reflect intent; update references to app.registered_commands only if still needed.python/sqlrooms-cli/sqlrooms/web/launcher.py (1)
376-376: Consider reducing SQL logging sensitivity.Line 376 logs user SQL at
infolevel; this can capture literals/secrets. Preferdebugand/or literal redaction.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` at line 376, Change the logging of user SQL from info to a lower-sensitivity level and ensure literals are redacted: replace the logger.info call that logs sql (the line invoking logger.info("project_query sql=%s", _normalize_sql_for_policy(sql)[:2000])) with a logger.debug call and/or pass a redacted version of sql (use or extend _normalize_sql_for_policy to strip or mask string/number literals before truncation) so SQL is not recorded at info level with sensitive literals.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.cursor/plans/sqlrooms_cli_app-builder_plan_(adjusted)_174b3a95.plan.md:
- Around line 216-223: Replace the absolute /Users/ilya/Workspace/sqlrooms.4/...
links in the plan markdown with repository-relative paths (e.g.,
packages/webcontainer/src/WebContainerSlice.ts ->
packages/webcontainer/src/WebContainerSlice.ts) so other contributors don't see
local machine paths; update each listed entry that begins with the
"/Users/ilya/Workspace/sqlrooms.4/" prefix to the corresponding repo-relative
path (remove the absolute prefix) in the .plan.md content.
In `@apps/sqlrooms-cli-ui/app-scaffolds/scaffolds.generated.json`:
- Around line 31-35: The generated vite.config.js is missing the React plugin so
JSX won't be transformed; update the scaffold file
(apps/sqlrooms-cli-ui/app-scaffolds/get-started/vite.config.js) to import and
register `@vitejs/plugin-react` by adding the react plugin to the config's plugins
array (i.e., ensure plugins: [react()] is present alongside the existing server
settings), and add `@vitejs/plugin-react` to the scaffold's package.json
devDependencies so the plugin is installed at build time.
In `@apps/sqlrooms-cli-ui/plugins/generateScaffolds.ts`:
- Around line 46-63: The function buildTreeForScaffold currently hardcodes
editableRel = 'src/App.jsx' and calls fs.readFile which will throw if that path
doesn't exist; update buildTreeForScaffold to first check for the file (e.g.,
fs.pathExists or fs.stat) or wrap the read in a try/catch, and if missing either
(a) search the built tree from buildDirectoryNode for a sensible fallback
editable file, (b) make the editable path configurable via a parameter, or (c)
return editable: null/undefined; ensure you reference and modify the editableRel
logic and the fs.readFile call inside buildTreeForScaffold so the function never
propagates an unhandled ENOENT.
In `@apps/sqlrooms-cli-ui/src/store.ts`:
- Around line 164-182: The updateSheetAppFiles updater uses deep spread
mutations; replace it with an Immer produce-based update like the upsertSheetApp
implementation: import produce if not present, then inside updateSheetAppFiles
call set(state => produce(state, draft => { const current =
draft.appProject.config.appsBySheetId[sheetId]; if (!current) return;
draft.appProject.config.appsBySheetId[sheetId] = { ...current, files, updatedAt:
Date.now() }; })); this keeps nested updates immutable and consistent with the
coding guideline.
- Around line 146-162: The upsertSheetApp setter uses many nested spreads which
is error-prone; replace the current nested spread update in upsertSheetApp with
an Immer produce() call that receives the previous state and mutates
draft.appProject.config.appsBySheetId[sheetId] (creating it if missing),
assigns/merges fields from the incoming app, and sets updatedAt = Date.now();
ensure you import produce and update the set((state)=>({...})) to
set(produce((draft)=>{...})) while keeping the same symbol names:
upsertSheetApp, appProject, config, appsBySheetId.
In `@examples/app-builder/src/components/Room.tsx`:
- Around line 7-19: The Room component must call room.initialize() on mount: add
a React useEffect inside the Room function that invokes
roomStore.getState().initialize() once (empty deps) so the store's lifecycle
hooks run; place this alongside the existing JSX return (before the return) and
keep RoomStateProvider/roomStore usage unchanged.
In `@packages/webcontainer/src/components/CodeView.tsx`:
- Around line 65-74: The Button's visibility and enabled state are inconsistent:
it is shown when hasDirty is true but disabled based on activeFile?.dirty.
Update the logic in the CodeView component so the Button is shown when hasDirty
is true, is enabled whenever hasDirty is true (i.e., disabled={!hasDirty}), and
the label reflects the multi-file action (use 'Save all' when hasDirty is true,
otherwise 'Saved'); adjust references to hasDirty, activeFile, Button and
saveAll accordingly so the button's presence, label and disabled state are
aligned.
In `@packages/webcontainer/src/components/filetree/fileSystemTreeToNodes.ts`:
- Around line 23-25: The comparator used when building entries (const entries =
Object.entries(subtree).sort(...)) only returns -1 when the first item is a
directory, which fails to handle the case where the second item is a directory;
update the comparator in fileSystemTreeToNodes.ts to explicitly check both items
using isDirectory(a[1]) and isDirectory(b[1]) returning -1 if a is directory and
b is not, 1 if b is directory and a is not, and otherwise fall back to
a[0].localeCompare(b[0]) so directories always come before files and same-type
items sort alphabetically.
In `@packages/webcontainer/src/WebContainerSlice.ts`:
- Around line 237-244: The resolved cwd returned by
get().webContainer.resolveProjectRoot() is never used when spawning the install
process; update the install logic (where instance.spawn('npm', ['install']) is
called inside the installDependencies flow) to pass the working directory option
(e.g., { cwd }) as the third argument so npm runs in the project root; keep the
existing state update (set(...) / produce(...)) and only add the {cwd} option to
instance.spawn to fix the unused variable.
In `@python/sqlrooms-cli/package.json`:
- Around line 16-21: The package.json contains a duplicated top-level
"dependencies" key; remove the redundant "dependencies" block so there is only a
single "dependencies" object (keeping the "sqlrooms-cli-app": "workspace:*"
entry), or merge entries if the two blocks ever differ, ensuring valid JSON with
one "dependencies" key referencing "sqlrooms-cli-app".
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Around line 58-60: The code calls json.loads(payload) when payload_json is a
string and can raise JSONDecodeError—wrap that json.loads call (the block where
it checks isinstance(payload, str)) in a try/except that catches
json.JSONDecodeError, emit a clear CLI-friendly error message referencing
payload_json (use the existing CLI logger or click.echo), and exit with a
non-zero status (or raise click.Abort) so the export fails gracefully instead of
showing a traceback.
- Around line 86-90: In the loop that writes files (the for path, content in
files.items() block) you're only stripping leading "/" from path and then
writing to target = root / rel which allows directory traversal like "../../…";
fix by resolving and validating the target is inside root before creating
directories or writing: compute rel = Path(str(path).lstrip("/")), compute
target = (root / rel).resolve(), compute root_resolved = root.resolve(), and if
target is not a subpath of root_resolved (e.g., via target == root_resolved or
str(target).startswith(str(root_resolved) + os.sep) or
Path(target).relative_to(root_resolved) without exception) raise an exception
and skip the file; only then call target.parent.mkdir(...) and
target.write_text(...). Ensure this check is applied in the function/method
containing that for loop to block path traversal.
- Around line 50-53: The code builds ui_ref from meta_namespace and interpolates
it into con.execute, which allows identifier injection; fix by sanitizing
meta_namespace before constructing ui_ref: either validate meta_namespace
against a safe identifier regex (e.g., /^[A-Za-z_][A-Za-z0-9_]*$/) and raise an
error on mismatch, or escape internal double quotes by replacing " with ""
before wrapping in double quotes; update the usage around ui_ref and the
con.execute call (the f"SELECT payload_json FROM {ui_ref} WHERE key = 'default'
LIMIT 1" statement) to use the sanitized/escaped value.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 73-75: The current _references_internal_namespace(sql, namespace)
regex misses quoted/delimited schema identifiers (e.g. "__sqlrooms".table), so
update the pattern to allow optional quoting characters around the namespace
(e.g. double quotes, backticks, square brackets) and still require the trailing
dot; specifically modify the regex built from namespace in
_references_internal_namespace to accept an optional opening and closing
quote/delimiter around re.escape(namespace) (while preserving the
(^|[^A-Za-z0-9_]) boundary and \s*\.), keep re.IGNORECASE, and ensure the
delimiter pairs are handled so quoted forms like "__sqlrooms".table or
`__sqlrooms`.table or [__sqlrooms].table are matched.
- Around line 378-387: The _run function currently calls
cur.execute(sql).fetchall() which loads the entire result set into memory;
change this to a bounded fetch pattern: execute the query with cur.execute(sql),
read the column names from cur.description, then fetch at most 5001 rows using
cur.fetchmany(5001) (or iterate and collect up to 5001) to populate rows and
detect truncation (truncated = len(fetched) > 5000), then slice to 5000 when
building the list of dicts (dict(zip(columns, row))) and compute rowCount from
the returned slice; update references to rows/limited accordingly so no
unbounded fetchall() is performed.
---
Nitpick comments:
In @.cursor/plans/sqlrooms_cli_app-builder_plan_(adjusted)_174b3a95.plan.md:
- Around line 224-257: Add an explicit build prerequisite sentence/note before
the example/UI validation steps (e.g., right before "Milestone 1: Artifact MVP"
or the Example/App run instructions) stating that packages must be built first
and providing the exact command to run: "Run pnpm build before running any
example app" to ensure workspace:* linked packages are resolved; reference the
workspace linking issue by mentioning "workspace:* links" so readers know why
the prerequisite is required.
In `@apps/sqlrooms-cli-ui/plugins/generateScaffolds.ts`:
- Around line 73-76: The current code in generateScaffolds.ts always calls
fs.readFile(abs, 'utf-8') and writes to result[entry.name].file.contents, which
will corrupt binary files; change the logic that builds result[entry.name] so
that you detect whether a file is binary (e.g., by checking extension or probing
contents after reading as a Buffer) and branch: for text files keep reading as
UTF-8 and set file.contents to the string, for binary files read as a Buffer and
store base64-encoded contents plus an explicit encoding flag (e.g.,
file.encoding = 'base64') so consumers can decode; update the code paths that
consume result[...].file to respect the new encoding field.
In `@apps/sqlrooms-cli-ui/plugins/scaffolds.ts`:
- Around line 21-23: Wrap the await runGenerate() call inside buildStart() with
a try-catch to prevent unhandled exceptions from
generateScaffoldsModule/runGenerate from crashing the dev server or failing
silently; in the catch block, log the error (use this.error(err) when available
in the plugin context, otherwise console.error) and rethrow or call this.error
to surface the failure to the build system so the error is visible.
- Around line 27-34: The onFsEvent handler currently calls runGenerate() for
every matching file event which can cause concurrent runs; modify the watcher
callbacks (watcher.on('add'/'change'/'unlink') -> onFsEvent) to
debounce/coalesce rapid events by adding a short timer (e.g., 100–300ms) and
clearing previous timers on new events, and ensure you avoid overlapping
executions by using a running flag or awaiting the single scheduled run; update
the symbols onFsEvent and runGenerate to use a shared debounceTimer and
optionally an isRunning flag so multiple events within the debounce window only
schedule one runGenerate and concurrent runs are prevented while respecting
scaffoldsRoot and outputFile checks.
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx`:
- Line 183: The declaration "const root: any = {}" in AppBuilderSheet.tsx loses
type safety; replace the any with a precise type such as Record<string, unknown>
or define a recursive interface (e.g., type Node = { [key: string]: Node |
unknown }) and use that for the root variable so consumers of root (and
functions interacting with it) get proper type checks; locate the root binding
in AppBuilderSheet.tsx and update its type, adjusting any downstream code with
narrow typings or type guards as needed.
- Around line 60-68: The effect dependencies include appState?.files which can
redundantly retrigger the effect alongside appState?.updatedAt and may re-run
when loadArtifactRuntime calls updateSheetAppFiles (even if patched.length > 0
prevents loops); remove appState?.files from the dependency array and depend
only on currentSheetId and appState?.updatedAt and a stable loadArtifactRuntime
(ensure loadArtifactRuntime is memoized with useCallback or otherwise stable) so
the effect only runs when the sheet or its update timestamp changes.
- Around line 293-325: The retry while loop in AppBuilderSheet.tsx around
ensureRunnableViteScaffold is ineffective because ensureRunnableViteScaffold
deterministically supplies the required files so `missing` will be empty after
the first call; remove the loop and call ensureRunnableViteScaffold once (or if
you intend future non-deterministic generation, add a clear TODO comment and
break/return after the first failure), then compute `required`, `missing`, and
return {status: 'ok'...} or push errors accordingly; specifically edit the block
that uses attempts, ensureRunnableViteScaffold, required/missing and errors so
it performs a single attempt (or documents retry intent) instead of looping.
In `@apps/sqlrooms-cli-ui/src/store.ts`:
- Line 248: The cast of scaffolds to Record<string, any> for initializing
filesTree is unsafe; update the code around filesTree and scaffolds (the
import/variable named scaffolds and the property 'get-started') to use a proper
typed interface or runtime validation: define a specific type (e.g.,
ScaffoldsMap with a 'get-started' entry shape) or add a Zod schema to validate
scaffolds after import, then use the validated value (or a safe fallback {})
when assigning filesTree so you don't bypass type checks and avoid silent
runtime failures.
In `@packages/webcontainer/src/components/CodeView.tsx`:
- Around line 23-25: The selector currently calls webContainer.hasDirtyFiles()
via useStoreWithWebContainer which can defeat selector memoization; instead
select the underlying state slice (e.g. webContainer.openedFiles or
webContainer.openedFilesMap) using useStoreWithWebContainer and compute hasDirty
locally in CodeView (replace hasDirty usage with a derived value computed from
the selected openedFiles), updating references to hasDirty,
useStoreWithWebContainer, and webContainer.hasDirtyFiles accordingly so the
selector only returns raw state and not a method call.
- Around line 77-86: The MonacoEditor currently hardcodes language="javascript",
so derive the editor language from the active file path: add a helper (e.g.,
getLanguageFromPath) that maps file extensions (js, jsx -> javascript; ts, tsx
-> typescript; json -> json; css -> css; html -> html; md -> markdown; default
-> plaintext), then pass language={activeFilePath ?
getLanguageFromPath(activeFilePath) : 'plaintext'} to the MonacoEditor; update
references to MonacoEditor, activeFilePath, activeFile, and updateFileContent
accordingly so other behavior is unchanged.
In `@packages/webcontainer/src/components/TerminalView.tsx`:
- Around line 18-42: The effect that creates and disposes the xterm instance
(uses Terminal, containerRef, terminalRef, lastIndexRef) should only run once on
mount/unmount — remove output from the dependency array so it becomes [] — so
the terminal is not destroyed/recreated whenever output changes; keep the
existing cleanup (term.dispose(), terminalRef.current = null,
lastIndexRef.current = 0) and rely on the other useEffect that incrementally
writes new output to synchronize terminal contents.
In `@packages/webcontainer/src/setFileContentInTree.ts`:
- Around line 6-8: normalizePath currently removes only a single leading slash
then collapses repeated slashes, so inputs like "//a//b" still end up with a
leading slash; update normalizePath to strip all leading slashes (e.g., remove
prefix matching one-or-more '/' characters) before collapsing consecutive
slashes so the function always returns a path without any leading '/' as
documented; reference normalizePath (and note callers like pathToSegments /
setFileContentInTree rely on normalized segments) and ensure the new
implementation is exported the same way.
In `@packages/webcontainer/src/WebContainerSlice.ts`:
- Around line 550-556: The nested Zustand state update for webContainer.output
in the setter should use produce() from Immer instead of object spread to remain
consistent and safe; locate the set call that currently does set((state) => ({
webContainer: { ...state.webContainer, output: state.webContainer.output + text
} })), replace it with a produce-based update that mutates
draft.webContainer.output (appending text) inside produce, ensuring you import/
use the existing produce symbol and keep other webContainer fields untouched.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Line 376: Change the logging of user SQL from info to a lower-sensitivity
level and ensure literals are redacted: replace the logger.info call that logs
sql (the line invoking logger.info("project_query sql=%s",
_normalize_sql_for_policy(sql)[:2000])) with a logger.debug call and/or pass a
redacted version of sql (use or extend _normalize_sql_for_policy to strip or
mask string/number literals before truncation) so SQL is not recorded at info
level with sensitive literals.
In `@python/sqlrooms-cli/tests/test_cli.py`:
- Around line 11-14: The test test_cli_version only checks
app.registered_commands which can miss invocation failures; update it to
actually invoke the CLI for the "export" command (e.g., use
click.testing.CliRunner to run "export --help"), assert the invocation exit_code
is 0 and that the output contains expected help text (like "Usage" or "export"),
and rename the test to something like test_cli_export_help to reflect intent;
update references to app.registered_commands only if still needed.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (44)
.cursor/plans/sqlrooms_cli_app-builder_plan_(adjusted)_174b3a95.plan.mdapps/sqlrooms-cli-ui/app-scaffolds/get-started/index.htmlapps/sqlrooms-cli-ui/app-scaffolds/get-started/package.jsonapps/sqlrooms-cli-ui/app-scaffolds/get-started/src/App.jsxapps/sqlrooms-cli-ui/app-scaffolds/get-started/src/main.jsxapps/sqlrooms-cli-ui/app-scaffolds/get-started/vite.config.jsapps/sqlrooms-cli-ui/app-scaffolds/scaffolds.generated.jsonapps/sqlrooms-cli-ui/package.jsonapps/sqlrooms-cli-ui/plugins/generateScaffolds.tsapps/sqlrooms-cli-ui/plugins/scaffolds.tsapps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxapps/sqlrooms-cli-ui/src/components/SheetsPanel.tsxapps/sqlrooms-cli-ui/src/store.tsapps/sqlrooms-cli-ui/vite.config.tsexamples/app-builder/package.jsonexamples/app-builder/src/components/CodeView.tsxexamples/app-builder/src/components/Room.tsxexamples/app-builder/src/store/store.tsexamples/app-builder/src/tools/getFileContent/getFileContentTool.tsexamples/app-builder/src/tools/listFiles/createListFilesTool.tsexamples/app-builder/src/tools/updateFileContent/updateFileContentTool.tsexamples/canvas/src/store.tsknip.jsonpackages/canvas/src/CanvasSlice.tspackages/cells/src/components/SheetsTabBar.tsxpackages/cells/src/types.tspackages/room-shell/src/RoomShellSlice.tspackages/webcontainer/package.jsonpackages/webcontainer/src/WebContainerSlice.tspackages/webcontainer/src/components/BrowserView.tsxpackages/webcontainer/src/components/CodeView.tsxpackages/webcontainer/src/components/TerminalView.tsxpackages/webcontainer/src/components/WebContainer.tsxpackages/webcontainer/src/components/WebContainerWorkbench.tsxpackages/webcontainer/src/components/filetree/FileTreeView.tsxpackages/webcontainer/src/components/filetree/fileSystemTreeToNodes.tspackages/webcontainer/src/index.tspackages/webcontainer/src/setFileContentInTree.tspython/sqlrooms-cli/package.jsonpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_cli.pypython/sqlrooms-server/sqlrooms/server/db_async.pyscripts/dev.mjs
💤 Files with no reviewable changes (2)
- examples/app-builder/src/components/CodeView.tsx
- python/sqlrooms-server/sqlrooms/server/db_async.py
✅ Files skipped from review due to trivial changes (2)
- apps/sqlrooms-cli-ui/app-scaffolds/get-started/package.json
- apps/sqlrooms-cli-ui/app-scaffolds/get-started/index.html
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/webcontainer/package.json
- examples/app-builder/src/tools/getFileContent/getFileContentTool.ts
- examples/app-builder/package.json
- packages/webcontainer/src/index.ts
- examples/app-builder/src/tools/listFiles/createListFilesTool.ts
| - [packages/webcontainer/src/WebContainerSlice.ts](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/src/WebContainerSlice.ts) | ||
| - [packages/webcontainer/src/index.ts](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/src/index.ts) | ||
| - [packages/webcontainer/package.json](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/package.json) | ||
| - [python/sqlrooms-server/sqlrooms/server/db_async.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-server/sqlrooms/server/db_async.py) | ||
| - [python/sqlrooms-cli/sqlrooms/web/launcher.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-cli/sqlrooms/web/launcher.py) | ||
| - [python/sqlrooms-cli/sqlrooms/cli.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-cli/sqlrooms/cli.py) | ||
| - [apps/sqlrooms-cli-ui/src/serverApi.ts](/Users/ilya/Workspace/sqlrooms.4/apps/sqlrooms-cli-ui/src/serverApi.ts) | ||
|
|
There was a problem hiding this comment.
Replace absolute local file links with repository-relative paths.
Lines 216-223 use /Users/ilya/... absolute paths, which are broken for other contributors and expose local machine path info. Use repo-relative links instead.
💡 Suggested doc fix
-- [packages/webcontainer/src/WebContainerSlice.ts](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/src/WebContainerSlice.ts)
-- [packages/webcontainer/src/index.ts](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/src/index.ts)
-- [packages/webcontainer/package.json](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/package.json)
-- [python/sqlrooms-server/sqlrooms/server/db_async.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-server/sqlrooms/server/db_async.py)
-- [python/sqlrooms-cli/sqlrooms/web/launcher.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-cli/sqlrooms/web/launcher.py)
-- [python/sqlrooms-cli/sqlrooms/cli.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-cli/sqlrooms/cli.py)
-- [apps/sqlrooms-cli-ui/src/serverApi.ts](/Users/ilya/Workspace/sqlrooms.4/apps/sqlrooms-cli-ui/src/serverApi.ts)
+- [packages/webcontainer/src/WebContainerSlice.ts](packages/webcontainer/src/WebContainerSlice.ts)
+- [packages/webcontainer/src/index.ts](packages/webcontainer/src/index.ts)
+- [packages/webcontainer/package.json](packages/webcontainer/package.json)
+- [python/sqlrooms-server/sqlrooms/server/db_async.py](python/sqlrooms-server/sqlrooms/server/db_async.py)
+- [python/sqlrooms-cli/sqlrooms/web/launcher.py](python/sqlrooms-cli/sqlrooms/web/launcher.py)
+- [python/sqlrooms-cli/sqlrooms/cli.py](python/sqlrooms-cli/sqlrooms/cli.py)
+- [apps/sqlrooms-cli-ui/src/serverApi.ts](apps/sqlrooms-cli-ui/src/serverApi.ts)📝 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.
| - [packages/webcontainer/src/WebContainerSlice.ts](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/src/WebContainerSlice.ts) | |
| - [packages/webcontainer/src/index.ts](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/src/index.ts) | |
| - [packages/webcontainer/package.json](/Users/ilya/Workspace/sqlrooms.4/packages/webcontainer/package.json) | |
| - [python/sqlrooms-server/sqlrooms/server/db_async.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-server/sqlrooms/server/db_async.py) | |
| - [python/sqlrooms-cli/sqlrooms/web/launcher.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-cli/sqlrooms/web/launcher.py) | |
| - [python/sqlrooms-cli/sqlrooms/cli.py](/Users/ilya/Workspace/sqlrooms.4/python/sqlrooms-cli/sqlrooms/cli.py) | |
| - [apps/sqlrooms-cli-ui/src/serverApi.ts](/Users/ilya/Workspace/sqlrooms.4/apps/sqlrooms-cli-ui/src/serverApi.ts) | |
| - [packages/webcontainer/src/WebContainerSlice.ts](packages/webcontainer/src/WebContainerSlice.ts) | |
| - [packages/webcontainer/src/index.ts](packages/webcontainer/src/index.ts) | |
| - [packages/webcontainer/package.json](packages/webcontainer/package.json) | |
| - [python/sqlrooms-server/sqlrooms/server/db_async.py](python/sqlrooms-server/sqlrooms/server/db_async.py) | |
| - [python/sqlrooms-cli/sqlrooms/web/launcher.py](python/sqlrooms-cli/sqlrooms/web/launcher.py) | |
| - [python/sqlrooms-cli/sqlrooms/cli.py](python/sqlrooms-cli/sqlrooms/cli.py) | |
| - [apps/sqlrooms-cli-ui/src/serverApi.ts](apps/sqlrooms-cli-ui/src/serverApi.ts) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.cursor/plans/sqlrooms_cli_app-builder_plan_(adjusted)_174b3a95.plan.md
around lines 216 - 223, Replace the absolute
/Users/ilya/Workspace/sqlrooms.4/... links in the plan markdown with
repository-relative paths (e.g., packages/webcontainer/src/WebContainerSlice.ts
-> packages/webcontainer/src/WebContainerSlice.ts) so other contributors don't
see local machine paths; update each listed entry that begins with the
"/Users/ilya/Workspace/sqlrooms.4/" prefix to the corresponding repo-relative
path (remove the absolute prefix) in the .plan.md content.
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (1)
packages/webcontainer/src/WebContainerSlice.ts (1)
194-230:⚠️ Potential issue | 🟠 MajorSet
serverStatustoerroron initialization failures.If
bootWebContainer()/mount()/install/start fails,initialize()throws but state may stay atinitializing/install-deps. Persisting failure state makes recovery and UX consistent.🛠️ Suggested fix
initialize: async (opts) => { @@ - set((state) => - produce(state, (draft) => { - draft.webContainer.serverStatus = {type: 'initializing'}; - }), - ); - const instance = await bootWebContainer(); - await instance.mount(get().webContainer.config.filesTree); - set((state) => - produce(state, (draft) => { - draft.webContainer.instance = instance; - }), - ); + set((state) => + produce(state, (draft) => { + draft.webContainer.serverStatus = {type: 'initializing'}; + }), + ); + try { + const instance = await bootWebContainer(); + await instance.mount(get().webContainer.config.filesTree); + set((state) => + produce(state, (draft) => { + draft.webContainer.instance = instance; + }), + ); @@ - try { - const exitCode = await get().webContainer.installDependencies(); - if (exitCode !== 0) { - throw new Error('Installation failed'); - } - } catch (error) { - throw new Error( - `WebContainer initialization failed during dependency installation: ${String(error)}`, - ); - } - - try { - await get().webContainer.startDevServer(); - } catch (error) { - throw new Error( - `WebContainer initialization failed while starting dev server: ${String(error)}`, - ); - } + const exitCode = await get().webContainer.installDependencies(); + if (exitCode !== 0) { + throw new Error('Installation failed'); + } + await get().webContainer.startDevServer(); + } catch (error) { + set((state) => + produce(state, (draft) => { + draft.webContainer.serverStatus = {type: 'error', error}; + }), + ); + throw new Error(`WebContainer initialization failed: ${String(error)}`); + } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerSlice.ts` around lines 194 - 230, In initialize() (WebContainerSlice.ts) ensure any failure updates webContainer.serverStatus to an error state before rethrowing: in the try/catch around bootWebContainer()/mount(), in the installDependencies() catch, and in the startDevServer() catch call set(state => produce(...)) to set draft.webContainer.serverStatus = { type: 'error', message: String(error) } (or include stage info like 'install-deps'/'start-dev' in the message) and persist draft.webContainer.instance = undefined if appropriate, then rethrow the error.
🧹 Nitpick comments (1)
packages/webcontainer/src/components/TerminalView.tsx (1)
18-42: Consider suppressing lint warning for intentional stale closure.The empty dependency array
[]is intentional here—you want to captureoutput's initial value once on mount, with subsequent updates handled by the second effect. However,eslint-plugin-react-hookswill flag this as a missing dependency.Consider adding an eslint-disable comment to make the intent explicit:
📝 Suggested clarification
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally capture initial output only on mount }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/components/TerminalView.tsx` around lines 18 - 42, The useEffect that initializes Terminal (creating term, calling term.open on containerRef.current, setting terminalRef.current and lastIndexRef.current based on output) intentionally has an empty dependency array and should suppress the eslint warning; add an eslint disable comment immediately above that useEffect (// eslint-disable-next-line react-hooks/exhaustive-deps) and include a brief inline comment explaining the intent (e.g., "intentional stale closure: initialize once on mount; updates handled by separate effect") so the linter won't flag the missing dependency on output while preserving the current behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/sqlrooms-cli-ui/plugins/scaffolds.ts`:
- Around line 57-63: In the fire-and-forget call to runGenerateSerial() in
scaffolds.ts, remove the `throw err` from the `.catch(...)` handler so the
caught error is logged but not rethrown (which creates an unhandled rejection in
dev-server watch mode); keep the existing console.error call but do not rethrow
the error in that catch for the void promise chain.
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx`:
- Around line 107-113: The "Run app" Button's onClick currently fires
loadArtifactRuntime(currentSheetId, appState.files || {}) without awaiting or
handling errors; change the handler to an async function that checks
currentSheetId, then awaits loadArtifactRuntime(...) inside a try-catch and
calls setStatus with the error message on failure (mirroring the pattern used in
onCreate). This ensures promise rejections from loadArtifactRuntime are caught
and the UI status is updated.
- Around line 298-305: The generated appFile string interpolates raw user values
(input.prompt and input.template) into JSX which can break compilation if they
contain braces or quotes; update the AppBuilderSheet.tsx code that builds the
appFile constant so it wraps these values with JSON.stringify (or an equivalent
JS string-escaping function) before embedding them into the template for the
default App component, ensuring special characters like {, }, ", and newlines
are properly escaped when inserted into the JSX string.
In `@packages/webcontainer/src/components/filetree/fileSystemTreeToNodes.ts`:
- Around line 19-32: In toChildren (function toChildren converting
FileSystemTree to TreeNodeData<FileNodeObject>), normalize the base path before
joining so you never produce `//` keys: trim any trailing slash from
currentBasePath (except when it is exactly "/") or use a small join helper to
ensure the produced path uses a single slash between segments, then compute the
node key/path from that normalized base; update the path construction used for
nodes in toChildren (where path is currently built with currentBasePath === '/'
? `/${name}` : `${currentBasePath}/${name}`) to use the normalized base so
consumers get stable tree identities.
In `@packages/webcontainer/src/WebContainerSlice.ts`:
- Around line 268-319: The startDevServer() flow can hang because
instance.spawn(...) return value (the spawned process) is ignored; change the
spawn usage in startDevServer() to capture the returned process (like
installDependencies() and runCommand() do), listen for its exit (process.exit or
equivalent) and if it exits before the 'server-ready' event resolve/reject the
outer Promise with an error, while still cleaning up offReady/offError
listeners; keep the existing 'server-ready' and 'error' handlers but add an exit
handler on the captured process to reject when the dev process terminates
prematurely and ensure settled is set and cleanup() is called.
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Around line 75-77: Check that the parsed JSON (payload / payload_json) is a
dict before calling .get to avoid AttributeError: add a guard before the
app_project = (payload or {}).get(...) line that verifies isinstance(payload,
dict) (or payload_json if that variable holds the parsed JSON); if not, print a
clear CLI error message and exit with non-zero status (e.g., sys.exit(1)).
Ensure this validation happens prior to accessing app_project, config, or
apps_by_sheet so the subsequent uses of app_project.get(...) are safe.
- Around line 88-90: The path segment built from sheet_id (root = out /
f"{safe_name}-{sheet_id[:8]}") can contain traversal characters and escape out;
sanitize the sheet_id segment before using it as a path component (e.g.,
restrict to a safe charset, replace/strip path separators, or replace with a
stable hash/slug of sheet_id) and then construct root from that sanitized
segment (referencing variables root, out, safe_name, sheet_id); after creating
root and computing root_resolved, additionally assert that root_resolved is a
descendant of out.resolve() (using .relative_to and raising/logging and aborting
if it is not) to defend against any remaining escape attempts.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 406-407: Wrap the await db_async.run_db_task(_run) call in a
try/except and translate execution errors into a consistent JSON 4xx client
response instead of allowing exceptions to propagate as 500s: catch the specific
DB/query exception types your DB layer exposes (e.g., QueryError/DatabaseError)
or, if none are available, catch Exception for query execution, and return
{"type":"json","data":{"error":"query_error","message": str(e)}} with an
appropriate 4xx status marker; leave truly unexpected errors to bubble or
re-raise as 500s. Ensure this change is made around the
db_async.run_db_task(_run) invocation in launcher.py so callers always receive
the structured JSON response on query failures.
---
Duplicate comments:
In `@packages/webcontainer/src/WebContainerSlice.ts`:
- Around line 194-230: In initialize() (WebContainerSlice.ts) ensure any failure
updates webContainer.serverStatus to an error state before rethrowing: in the
try/catch around bootWebContainer()/mount(), in the installDependencies() catch,
and in the startDevServer() catch call set(state => produce(...)) to set
draft.webContainer.serverStatus = { type: 'error', message: String(error) } (or
include stage info like 'install-deps'/'start-dev' in the message) and persist
draft.webContainer.instance = undefined if appropriate, then rethrow the error.
---
Nitpick comments:
In `@packages/webcontainer/src/components/TerminalView.tsx`:
- Around line 18-42: The useEffect that initializes Terminal (creating term,
calling term.open on containerRef.current, setting terminalRef.current and
lastIndexRef.current based on output) intentionally has an empty dependency
array and should suppress the eslint warning; add an eslint disable comment
immediately above that useEffect (// eslint-disable-next-line
react-hooks/exhaustive-deps) and include a brief inline comment explaining the
intent (e.g., "intentional stale closure: initialize once on mount; updates
handled by separate effect") so the linter won't flag the missing dependency on
output while preserving the current behavior.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (32)
.cursor/plans/cells_module_architecture_review_d232d6c4.plan.md.cursor/plans/extensible_cell_types_a0d19eef.plan.md.cursor/plans/extensible_cells_and_dagslice_merge_d21cb4bf.plan.md.cursor/plans/mosaic_client_abstraction_fea347b1.plan.md.cursor/plans/mosaic_client_state_refactor_57fc8f4c.plan.md.cursor/plans/multi-db-duckdb-core-architecture_b61e5166.plan.md.cursor/plans/refactor_example_apps_5968b022.plan.md.cursor/plans/store_cell_results_directly_c642850f.plan.md.cursor/plans/typed_sheets_architecture_db76d267.plan.md.cursor/plans/unified_cell_architecture_a10fd68f.plan.md.cursor/plans/unified_cells_architecture_993ea118.plan.md.cursor/plans/unified_sheets_architecture_2b482395.plan.md.gitignoreapps/sqlrooms-cli-ui/app-scaffolds/get-started/package.jsonapps/sqlrooms-cli-ui/app-scaffolds/get-started/vite.config.jsapps/sqlrooms-cli-ui/app-scaffolds/scaffolds.generated.jsonapps/sqlrooms-cli-ui/package.jsonapps/sqlrooms-cli-ui/plugins/generateScaffolds.tsapps/sqlrooms-cli-ui/plugins/scaffolds.tsapps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxapps/sqlrooms-cli-ui/src/helpers.tsapps/sqlrooms-cli-ui/src/store.tsknip.jsonpackages/webcontainer/src/WebContainerSlice.tspackages/webcontainer/src/components/CodeView.tsxpackages/webcontainer/src/components/TerminalView.tsxpackages/webcontainer/src/components/filetree/fileSystemTreeToNodes.tspackages/webcontainer/src/setFileContentInTree.tspython/sqlrooms-cli/package.jsonpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_cli.py
💤 Files with no reviewable changes (12)
- .cursor/plans/extensible_cell_types_a0d19eef.plan.md
- .cursor/plans/mosaic_client_state_refactor_57fc8f4c.plan.md
- .cursor/plans/extensible_cells_and_dagslice_merge_d21cb4bf.plan.md
- .cursor/plans/unified_cells_architecture_993ea118.plan.md
- .cursor/plans/multi-db-duckdb-core-architecture_b61e5166.plan.md
- .cursor/plans/store_cell_results_directly_c642850f.plan.md
- .cursor/plans/mosaic_client_abstraction_fea347b1.plan.md
- .cursor/plans/refactor_example_apps_5968b022.plan.md
- .cursor/plans/typed_sheets_architecture_db76d267.plan.md
- .cursor/plans/cells_module_architecture_review_d232d6c4.plan.md
- .cursor/plans/unified_sheets_architecture_2b482395.plan.md
- .cursor/plans/unified_cell_architecture_a10fd68f.plan.md
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/sqlrooms-cli-ui/app-scaffolds/get-started/package.json
- python/sqlrooms-cli/tests/test_cli.py
- knip.json
- packages/webcontainer/src/components/CodeView.tsx
- apps/sqlrooms-cli-ui/app-scaffolds/get-started/vite.config.js
- apps/sqlrooms-cli-ui/package.json
| const appFile = `import React from 'react'; | ||
|
|
||
| export default function App() { | ||
| return ( | ||
| <main style={{padding: 16}}> | ||
| <h1>${appTitle}</h1> | ||
| <p>Prompt: ${input.prompt.replace(/</g, '<')}</p> | ||
| <p>Template: ${input.template}</p> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx | sed -n '290,310p'Repository: sqlrooms/sqlrooms
Length of output: 746
🏁 Script executed:
# Check for similar patterns in the file
rg -n '\$\{input\.' apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx | head -20Repository: sqlrooms/sqlrooms
Length of output: 168
🏁 Script executed:
# Look for how appFile is used after creation
sed -n '298,330p' apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxRepository: sqlrooms/sqlrooms
Length of output: 837
🏁 Script executed:
# Check the function signature and context
sed -n '265,298p' apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxRepository: sqlrooms/sqlrooms
Length of output: 769
🏁 Script executed:
# Check what happens if input contains problematic characters
# Let's search for test cases or usage of this function
rg -n "generateAppFromPromptLocal" apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx -A 5 -B 5Repository: sqlrooms/sqlrooms
Length of output: 707
🏁 Script executed:
# Check if there are any test cases or examples showing what input values look like
rg -n "prompt:|template:" apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx -B 3 -A 3 | head -30Repository: sqlrooms/sqlrooms
Length of output: 527
🏁 Script executed:
# Let's check if there are any examples of what input values might contain
rg -n "prompt" apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx | head -15Repository: sqlrooms/sqlrooms
Length of output: 591
🏁 Script executed:
# Let's check how appFile is actually used - is it parsed/compiled as JSX later?
rg -n "ensureRunnableViteScaffold" apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx -B 2 -A 10Repository: sqlrooms/sqlrooms
Length of output: 1456
Escape user input values before embedding in generated JSX to prevent syntax errors.
The prompt and template inputs are directly interpolated into generated JSX code without proper escaping. If either input contains characters like {, }, or other special characters, they will break the generated code at compile time. For example, a prompt containing "Show {count}" would create invalid JSX: <p>Prompt: Show {count}</p>, where {count} is interpreted as a variable reference.
Use JSON.stringify() to safely escape these values:
🛠️ Suggested fix
const appFile = `import React from 'react';
export default function App() {
return (
<main style={{padding: 16}}>
<h1>${appTitle}</h1>
+ const promptLiteral = JSON.stringify(input.prompt);
+ const templateLiteral = JSON.stringify(input.template);
- <p>Prompt: ${input.prompt.replace(/</g, '<')}</p>
- <p>Template: ${input.template}</p>
+ <p>Prompt: {${promptLiteral}}</p>
+ <p>Template: {${templateLiteral}}</p>
</main>
);
}
`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` around lines 298 -
305, The generated appFile string interpolates raw user values (input.prompt and
input.template) into JSX which can break compilation if they contain braces or
quotes; update the AppBuilderSheet.tsx code that builds the appFile constant so
it wraps these values with JSON.stringify (or an equivalent JS string-escaping
function) before embedding them into the template for the default App component,
ensuring special characters like {, }, ", and newlines are properly escaped when
inserted into the JSX string.
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)
406-416: Error response structure is inconsistent with other endpoints.Other endpoints like
execute_queryandfetch_arrowreturn{"error": str(exc)}directly. This endpoint wraps errors in{"type": "json", "data": {"error": ..., "message": ...}}. Additionally, thetypewrapper appears on both success and error responses, which may complicate client handling.Consider aligning with the existing pattern:
♻️ Proposed fix for consistent error format
try: data = await db_async.run_db_task(_run) except Exception as exc: + logger.debug("project_query failed: %s", exc) return JSONResponse( - { - "type": "json", - "data": {"error": "query_error", "message": str(exc)}, - }, + {"error": str(exc)}, status_code=400, ) - return {"type": "json", "data": data} + return data🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 406 - 416, The error response in the try/except around db_async.run_db_task(_run) uses a nested {"type":"json","data":{...}} format while other handlers (e.g. execute_query, fetch_arrow) return errors as {"error": str(exc)} and success payloads directly; change the except block to return JSONResponse({"error": str(exc)}, status_code=400) and adjust the success return to return the data payload directly (remove the {"type":"json","data":...} wrapper), updating the JSONResponse use and the final return statement that currently returns {"type":"json","data": data}.python/sqlrooms-cli/sqlrooms/cli.py (2)
48-50: Redundantresolve()call.
outis already resolved at line 48 via.resolve(). Theout_resolvedassignment at line 50 is redundant—you can useoutdirectly in the validation at line 107.♻️ Suggested simplification
out = Path(out_dir).expanduser().resolve() out.mkdir(parents=True, exist_ok=True) - out_resolved = out.resolve()Then replace
out_resolvedwithoutat line 107:- root_resolved.relative_to(out_resolved) + root_resolved.relative_to(out)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/cli.py` around lines 48 - 50, The code resolves the output path twice: remove the redundant out_resolved variable by keeping out = Path(out_dir).expanduser().resolve() and delete the out_resolved = out.resolve() line; then update any uses of out_resolved (e.g., the validation at line 107) to use out instead so all checks operate on the already-resolved Path object (references: out, out_resolved, out_dir, Path).
87-87: Loop variableappshadows globalapp.The loop variable shadows the module-level
app = typer.Typer(...)at line 21. Consider renaming tosheet_apporapp_datato avoid confusion during future maintenance.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/cli.py` at line 87, The loop variable name "app" shadows the module-level Typer instance "app" and should be renamed (e.g., to "sheet_app" or "app_data") to avoid confusion; update the for-loop over "apps_by_sheet.items()" and all references inside that loop to use the new name, ensuring any uses of the module-level "app" elsewhere remain unchanged (refer to the module-level "app = typer.Typer(...)" and the for loop "for sheet_id, app in apps_by_sheet.items()" to locate the change).packages/webcontainer/src/WebContainerSlice.ts (2)
547-551: Hardcoded fallback path may not exist in all project structures.The fallback
'/src/App.jsx'assumes a specific project structure. If the project uses TypeScript (.tsx) or a different entry point, this could result in attempting to open a non-existent file.Consider using
getFirstFilePathFromTreeas a final fallback, which already exists in this file:♻️ Suggested improvement
const nextActive = nextActiveFromRequest ?? nextActiveFromPrev ?? preservedOpened[0]?.path ?? - '/src/App.jsx'; + getFirstFilePathFromTree(filesTree) ?? + null;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerSlice.ts` around lines 547 - 551, The current fallback for nextActive uses a hardcoded '/src/App.jsx' which may not exist; change the final fallback to call getFirstFilePathFromTree(...) instead so nextActive is set to nextActiveFromRequest ?? nextActiveFromPrev ?? preservedOpened[0]?.path ?? getFirstFilePathFromTree(tree) (or appropriate tree/roots value available in this scope), referencing nextActive, nextActiveFromRequest, nextActiveFromPrev, preservedOpened and getFirstFilePathFromTree to locate and update the line.
578-579: TODO: stderr capture is not implemented.The function signature promises
stderrin the return value but always returns an empty string. Consider either implementing stderr capture or documenting this limitation in the function's JSDoc.Note: WebContainer's
spawnoutput stream combines stdout and stderr by default. If separate streams are needed, you may need to explore terminal mode options.,
Would you like me to help implement stderr capture or open an issue to track this?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/webcontainer/src/WebContainerSlice.ts` around lines 578 - 579, The code currently declares const stderr = '' and never captures error output; update the implementation around the WebContainer spawn handling in WebContainerSlice.ts to actually collect stderr by (a) calling spawn with terminal: false (or disabling combined terminal mode) so the returned process.output emits objects with a type field, then append chunks where chunk.type === 'stderr' to a mutable stderr string (similar to how stdout is accumulated into stdout) and return it; alternatively, if you choose not to change spawn/terminal mode, update the function JSDoc for the enclosing function to document that stderr is not separately captured because WebContainer combines streams by default.apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx (1)
322-333: Validation logic appears redundant.The
missingcheck will never find any files becauseensureRunnableViteScaffold(called on line 319) guarantees that/index.html,/src/main.jsx,/src/App.jsx, and/package.jsonall exist in the returnedfilesarray.This validation could be useful if
ensureRunnableViteScaffoldbehavior changes, but currently it adds complexity without providing value. Consider either:
- Removing this dead code path, or
- Moving the validation inside
ensureRunnableViteScaffoldwhere it would be meaningful🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` around lines 322 - 333, The block computing required/missing and erroring is redundant because ensureRunnableViteScaffold already guarantees those paths; remove the missing-files validation (the const required = new Set(...), const missing = [...].filter(...), the missing.length check and errors.push/return error) from AppBuilderSheet.tsx so the function simply returns {status: 'ok', attempts, errors, files} after ensureRunnableViteScaffold, or alternatively move equivalent validation into ensureRunnableViteScaffold if you prefer the check to live with the scaffold creation logic; adjust any callers of ensureRunnableViteScaffold if you choose to move the validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Around line 91-104: Compute safe_sheet_id before constructing safe_name and
use that sanitized safe_sheet_id as the fallback instead of the raw sheet_id
(i.e. replace the fallback at safe_name = ... or sheet_id with safe_sheet_id),
and defer creating the directory (move the root.mkdir(parents=True,
exist_ok=True) call) until after the subsequent validation that ensures the path
is inside out; update references to safe_sheet_id, safe_name, root and the mkdir
call accordingly so no filesystem write occurs before validation.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 70-78: The check in _is_select_only_sql currently uses literal
spaces and will miss valid SQL with tabs/newlines; update _is_select_only_sql to
use a case-insensitive regex (e.g. r'^(select|with)\s') to match the keyword
followed by any whitespace character, import and use the re module, and keep the
existing normalization via _normalize_sql_for_policy and the single-statement
semicolon check unchanged so the function still returns True only for
select/with statements.
---
Nitpick comments:
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx`:
- Around line 322-333: The block computing required/missing and erroring is
redundant because ensureRunnableViteScaffold already guarantees those paths;
remove the missing-files validation (the const required = new Set(...), const
missing = [...].filter(...), the missing.length check and errors.push/return
error) from AppBuilderSheet.tsx so the function simply returns {status: 'ok',
attempts, errors, files} after ensureRunnableViteScaffold, or alternatively move
equivalent validation into ensureRunnableViteScaffold if you prefer the check to
live with the scaffold creation logic; adjust any callers of
ensureRunnableViteScaffold if you choose to move the validation.
In `@packages/webcontainer/src/WebContainerSlice.ts`:
- Around line 547-551: The current fallback for nextActive uses a hardcoded
'/src/App.jsx' which may not exist; change the final fallback to call
getFirstFilePathFromTree(...) instead so nextActive is set to
nextActiveFromRequest ?? nextActiveFromPrev ?? preservedOpened[0]?.path ??
getFirstFilePathFromTree(tree) (or appropriate tree/roots value available in
this scope), referencing nextActive, nextActiveFromRequest, nextActiveFromPrev,
preservedOpened and getFirstFilePathFromTree to locate and update the line.
- Around line 578-579: The code currently declares const stderr = '' and never
captures error output; update the implementation around the WebContainer spawn
handling in WebContainerSlice.ts to actually collect stderr by (a) calling spawn
with terminal: false (or disabling combined terminal mode) so the returned
process.output emits objects with a type field, then append chunks where
chunk.type === 'stderr' to a mutable stderr string (similar to how stdout is
accumulated into stdout) and return it; alternatively, if you choose not to
change spawn/terminal mode, update the function JSDoc for the enclosing function
to document that stderr is not separately captured because WebContainer combines
streams by default.
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Around line 48-50: The code resolves the output path twice: remove the
redundant out_resolved variable by keeping out =
Path(out_dir).expanduser().resolve() and delete the out_resolved = out.resolve()
line; then update any uses of out_resolved (e.g., the validation at line 107) to
use out instead so all checks operate on the already-resolved Path object
(references: out, out_resolved, out_dir, Path).
- Line 87: The loop variable name "app" shadows the module-level Typer instance
"app" and should be renamed (e.g., to "sheet_app" or "app_data") to avoid
confusion; update the for-loop over "apps_by_sheet.items()" and all references
inside that loop to use the new name, ensuring any uses of the module-level
"app" elsewhere remain unchanged (refer to the module-level "app =
typer.Typer(...)" and the for loop "for sheet_id, app in apps_by_sheet.items()"
to locate the change).
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 406-416: The error response in the try/except around
db_async.run_db_task(_run) uses a nested {"type":"json","data":{...}} format
while other handlers (e.g. execute_query, fetch_arrow) return errors as
{"error": str(exc)} and success payloads directly; change the except block to
return JSONResponse({"error": str(exc)}, status_code=400) and adjust the success
return to return the data payload directly (remove the
{"type":"json","data":...} wrapper), updating the JSONResponse use and the final
return statement that currently returns {"type":"json","data": data}.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/sqlrooms-cli-ui/plugins/scaffolds.tsapps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxpackages/db/src/DbSlice.tspackages/db/src/helpers.tspackages/webcontainer/src/WebContainerSlice.tspackages/webcontainer/src/components/TerminalView.tsxpackages/webcontainer/src/components/filetree/fileSystemTreeToNodes.tspython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/launcher.py
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/sqlrooms-cli-ui/plugins/scaffolds.ts
…me target-placement policy Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/db/src/DbSlice.ts (1)
87-93:⚠️ Potential issue | 🔴 CriticalEscape the attached database alias before SQL interpolation.
At Line 92,
attachedNameis interpolated directly intoATTACH ... AS "...". Since this value can flow frommaterializedDatabase, a quote in input can break the statement or inject extra SQL.🔧 Suggested fix
- await core.query(`ATTACH ':memory:' AS "${attachedName}"`); + await core.query(`ATTACH ':memory:' AS ${escapeId(attachedName)}`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/db/src/DbSlice.ts` around lines 87 - 93, The attached database alias (attachedName) is interpolated directly into core.query(`ATTACH ':memory:' AS "${attachedName}"`) which allows quotes or malicious input from materializedDatabase/get().db.config.coreMaterialization.attachedDatabaseName to break or inject SQL; before interpolating, escape any double-quote characters in attachedName by replacing " with "" (SQLite identifier escaping) and then interpolate the sanitized identifier inside the double-quoted AS "..." clause so core.query is fed a safely quoted identifier.
🧹 Nitpick comments (1)
python/sqlrooms-server/sqlrooms/server/server.py (1)
39-41: Duplicate_quote_identimplementation.This function is identical to
_quote_identinpython/sqlrooms-server/sqlrooms/server/db_async.py(lines 227-229). Consider importing fromdb_asyncto avoid duplication.♻️ Proposed fix
-def _quote_ident(ident: str) -> str: - return '"' + ident.replace('"', '""') + '"' +from .db_async import _quote_identNote: You may need to export
_quote_identfromdb_async.pyif it's not already public.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-server/sqlrooms/server/server.py` around lines 39 - 41, The _quote_ident implementation in server.py duplicates the identical function in db_async.py; remove the duplicate by importing the single implementation from db_async (exporting it there if necessary). Replace the local _quote_ident definition in server.py with an import of _quote_ident from sqlrooms.server.db_async (or adjust the export in db_async.py to make _quote_ident public), and ensure all callers in server.py use the imported symbol to avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/sqlrooms-cli-ui/package.json`:
- Line 40: The package lists "immer" in devDependencies but it's imported at
runtime (produce used in WebContainerSlice.ts), so move "immer" from
devDependencies to dependencies in package.json to ensure it's installed for
consumers; update package.json by removing the "immer" entry under
devDependencies and adding the same version string under dependencies (keep the
existing version "^11.0.1") so WebContainerSlice.ts's produce() calls work when
this package is consumed.
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx`:
- Around line 68-69: finalName currently uses (name ||
deriveAppName(prompt)).trim(), which treats whitespace-only name as valid and
trims to empty; change logic to trim name first and only fall back to
deriveAppName(prompt) when the trimmed name is empty. In other words, call
name?.trim() (or trim into a local variable), if that result is non-empty use
it, otherwise call deriveAppName(prompt) to produce finalName, then continue to
setBusy(true); update references to finalName accordingly.
- Around line 60-63: The effect calls loadArtifactRuntime(currentSheetId,
appState.files) without handling rejections, so failures from applyFilesTree()
or initializeWebContainer() leave status unchanged; wrap the call in an async
IIFE inside the React.useEffect and await loadArtifactRuntime, then catch any
error and call setStatus(...) with a clear failure state/message (include
error.message), ensuring you still guard for !currentSheetId || !appState?.files
and keep the same dependencies (currentSheetId, appState?.updatedAt,
loadArtifactRuntime).
In `@packages/cells/src/execution.ts`:
- Around line 60-63: finalDatabaseName is being set to
coreMaterialization.attachedDatabaseName even when routed-query support
(dbConnectors?.runQuery) is absent, causing SQL to be qualified against the
wrong DB; change the logic that computes and uses finalDatabaseName so it only
uses state.db.config.coreMaterialization.attachedDatabaseName when
dbConnectors?.runQuery is truthy (otherwise leave finalDatabaseName undefined),
and apply the same guard to the other occurrences that qualify SQL (the nearby
blocks around the current assignment and the blocks at the other mentioned
sites) so fallback materialization continues to target db.currentDatabase when
routed queries are unavailable.
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Around line 82-86: The code assumes payload["appProject"], config, and
appsBySheetId are dicts before calling .get() and .items(), which can raise
AttributeError; before using app_project = (payload or {}).get("appProject")...,
validate types like in the earlier pattern (lines ~75-80): check that payload is
a dict and that app_project and config are dicts (fallback to {} or return
early) and ensure apps_by_sheet is a dict before iterating; update the
assignments for app_project, config, and apps_by_sheet and guard the for
sheet_id, sheet_app in apps_by_sheet.items() loop so it only runs when
apps_by_sheet is a mapping to avoid crashes in functions/variables named
payload, app_project, config, apps_by_sheet, exported and the loop over
sheet_id/sheet_app.
In `@python/sqlrooms-server/sqlrooms/server/server.py`:
- Around line 116-127: The code interpolates user-derived target_rel into SQL in
_upload (cur.execute) without quoting, enabling SQL injection; fix by validating
and safely quoting the identifier before use: in _upload, ensure target_rel
(from table_name) matches a strict pattern (e.g., /^[A-Za-z_][A-Za-z0-9_]*$/) or
raise, then construct a safe quoted identifier by escaping any double quotes
(replace " with "") and wrapping in double quotes, and use that safe identifier
in the CREATE OR REPLACE TABLE ... AS SELECT * FROM {tmp_rel_q} executed by
cur.execute; keep tmp_rel_q usage unchanged.
---
Outside diff comments:
In `@packages/db/src/DbSlice.ts`:
- Around line 87-93: The attached database alias (attachedName) is interpolated
directly into core.query(`ATTACH ':memory:' AS "${attachedName}"`) which allows
quotes or malicious input from
materializedDatabase/get().db.config.coreMaterialization.attachedDatabaseName to
break or inject SQL; before interpolating, escape any double-quote characters in
attachedName by replacing " with "" (SQLite identifier escaping) and then
interpolate the sanitized identifier inside the double-quoted AS "..." clause so
core.query is fed a safely quoted identifier.
---
Nitpick comments:
In `@python/sqlrooms-server/sqlrooms/server/server.py`:
- Around line 39-41: The _quote_ident implementation in server.py duplicates the
identical function in db_async.py; remove the duplicate by importing the single
implementation from db_async (exporting it there if necessary). Replace the
local _quote_ident definition in server.py with an import of _quote_ident from
sqlrooms.server.db_async (or adjust the export in db_async.py to make
_quote_ident public), and ensure all callers in server.py use the imported
symbol to avoid duplication.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
python/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (17)
apps/sqlrooms-cli-ui/package.jsonapps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxapps/sqlrooms-cli-ui/src/components/DataSourcesPanel.tsxapps/sqlrooms-cli-ui/src/layout.tsapps/sqlrooms-cli-ui/src/runtimeConfig.tsapps/sqlrooms-cli-ui/src/store.tspackages/cells/src/execution.tspackages/cells/src/sqlHelpers.tspackages/db/src/DbSlice.tspackages/db/src/types.tspackages/duckdb/src/connectors/WebSocketDuckDbConnector.tspackages/webcontainer/src/WebContainerSlice.tspython/sqlrooms-cli/package.jsonpython/sqlrooms-cli/pyproject.tomlpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-server/sqlrooms/server/server.py
| "@types/react-dom": "^19.2.3", | ||
| "@vitejs/plugin-react": "^5.1.2", | ||
| "eslint": "^9.36.0", | ||
| "immer": "^11.0.1", |
There was a problem hiding this comment.
immer should be in dependencies, not devDependencies.
The immer package is used at runtime in WebContainerSlice.ts (via produce() for state updates). Placing it in devDependencies means it won't be available when this package is consumed as a dependency.
🐛 Proposed fix
"dependencies": {
"@sqlrooms/ai": "workspace:*",
...
+ "immer": "^11.0.1",
...
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
...
- "immer": "^11.0.1",
...
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/sqlrooms-cli-ui/package.json` at line 40, The package lists "immer" in
devDependencies but it's imported at runtime (produce used in
WebContainerSlice.ts), so move "immer" from devDependencies to dependencies in
package.json to ensure it's installed for consumers; update package.json by
removing the "immer" entry under devDependencies and adding the same version
string under dependencies (keep the existing version "^11.0.1") so
WebContainerSlice.ts's produce() calls work when this package is consumed.
| React.useEffect(() => { | ||
| if (!currentSheetId || !appState?.files) return; | ||
| void loadArtifactRuntime(currentSheetId, appState.files); | ||
| }, [currentSheetId, appState?.updatedAt, loadArtifactRuntime]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the AppBuilderSheet.tsx file to understand the full context
cat -n apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx | head -100Repository: sqlrooms/sqlrooms
Length of output: 4477
🏁 Script executed:
# Continue reading to see the Run button handler and remaining code
cat -n apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx | sed -n '100,180p'Repository: sqlrooms/sqlrooms
Length of output: 3416
🏁 Script executed:
# Let's verify if there are any other calls to loadArtifactRuntime to confirm the error handling pattern
rg "loadArtifactRuntime" apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsxRepository: sqlrooms/sqlrooms
Length of output: 344
🏁 Script executed:
# Check the implementations of applyFilesTree and initializeWebContainer to confirm they can throw
rg "applyFilesTree|initializeWebContainer" apps/sqlrooms-cli-ui/src/store --max-count 20 -A 3Repository: sqlrooms/sqlrooms
Length of output: 131
🏁 Script executed:
# Find where the store is defined
find . -type f -name "*store*" | grep -E "\.(ts|tsx)$" | head -20Repository: sqlrooms/sqlrooms
Length of output: 752
🏁 Script executed:
# Check the webContainer slice in the store to see if applyFilesTree and initializeWebContainer can throw
cat -n apps/sqlrooms-cli-ui/src/store.ts | grep -A 30 "webContainer"Repository: sqlrooms/sqlrooms
Length of output: 1274
🏁 Script executed:
# Search for WebContainerSliceConfig to understand applyFilesTree and initializeWebContainer
rg "WebContainerSliceConfig|applyFilesTree|initializeWebContainer" --max-count 20 -A 5 -B 2Repository: sqlrooms/sqlrooms
Length of output: 10966
🏁 Script executed:
# Check the implementation of applyFilesTree and initializeWebContainer in the WebContainerSlice
cat -n packages/webcontainer/src/WebContainerSlice.ts | sed -n '1,50p'Repository: sqlrooms/sqlrooms
Length of output: 1714
🏁 Script executed:
# Search for the applyFilesTree and initializeWebContainer method implementations
sed -n '100,300p' packages/webcontainer/src/WebContainerSlice.tsRepository: sqlrooms/sqlrooms
Length of output: 6765
🏁 Script executed:
# Search for the applyFilesTree implementation
rg "async applyFilesTree" packages/webcontainer/src/WebContainerSlice.ts -A 40Repository: sqlrooms/sqlrooms
Length of output: 1550
🏁 Script executed:
# Get more of the applyFilesTree implementation
rg "async applyFilesTree" packages/webcontainer/src/WebContainerSlice.ts -A 60Repository: sqlrooms/sqlrooms
Length of output: 2260
Handle async errors in the auto-load effect.
loadArtifactRuntime(...) is fired without a catch here. If it rejects, the rejection is unhandled and status never reflects the failure. Both applyFilesTree() and initializeWebContainer() are async operations that can throw errors. The other call sites of loadArtifactRuntime (in onCreate and the "Run app" button handler) properly handle errors with try-catch and call setStatus() to report them; the effect should follow the same pattern.
Suggested fix
React.useEffect(() => {
if (!currentSheetId || !appState?.files) return;
- void loadArtifactRuntime(currentSheetId, appState.files);
+ void loadArtifactRuntime(currentSheetId, appState.files).catch((error) => {
+ setStatus(error instanceof Error ? error.message : String(error));
+ });
}, [currentSheetId, appState?.updatedAt, loadArtifactRuntime]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` around lines 60 -
63, The effect calls loadArtifactRuntime(currentSheetId, appState.files) without
handling rejections, so failures from applyFilesTree() or
initializeWebContainer() leave status unchanged; wrap the call in an async IIFE
inside the React.useEffect and await loadArtifactRuntime, then catch any error
and call setStatus(...) with a clear failure state/message (include
error.message), ensuring you still guard for !currentSheetId || !appState?.files
and keep the same dependencies (currentSheetId, appState?.updatedAt,
loadArtifactRuntime).
| const finalName = (name || deriveAppName(prompt)).trim(); | ||
| setBusy(true); |
There was a problem hiding this comment.
Prevent blank app/sheet names when name is whitespace-only.
A whitespace name bypasses the fallback and trims to an empty string, which can rename the sheet/app to blank.
Suggested fix
- const finalName = (name || deriveAppName(prompt)).trim();
+ const finalName = (name.trim() || deriveAppName(prompt)).trim();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/sqlrooms-cli-ui/src/components/AppBuilderSheet.tsx` around lines 68 -
69, finalName currently uses (name || deriveAppName(prompt)).trim(), which
treats whitespace-only name as valid and trims to empty; change logic to trim
name first and only fall back to deriveAppName(prompt) when the trimmed name is
empty. In other words, call name?.trim() (or trim into a local variable), if
that result is non-empty use it, otherwise call deriveAppName(prompt) to produce
finalName, then continue to setBusy(true); update references to finalName
accordingly.
| const finalDatabaseName = | ||
| state.db.config.coreMaterialization.strategy === 'attached_ephemeral' | ||
| ? state.db.config.coreMaterialization.attachedDatabaseName | ||
| : undefined; |
There was a problem hiding this comment.
Guard finalDatabaseName behind routed-query availability to avoid fallback misrouting.
When dbConnectors?.runQuery is unavailable, the fallback path still materializes into db.currentDatabase, but SQL qualification now uses attachedDatabaseName. That can make local table references point to the wrong database.
🔧 Suggested fix
- const finalDatabaseName =
- state.db.config.coreMaterialization.strategy === 'attached_ephemeral'
- ? state.db.config.coreMaterialization.attachedDatabaseName
- : undefined;
+ const hasRoutedRunQuery = Boolean(state.db.connectors?.runQuery);
+ const finalDatabaseName =
+ hasRoutedRunQuery &&
+ state.db.config.coreMaterialization.strategy === 'attached_ephemeral'
+ ? state.db.config.coreMaterialization.attachedDatabaseName
+ : undefined;Also applies to: 80-84, 130-139
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cells/src/execution.ts` around lines 60 - 63, finalDatabaseName is
being set to coreMaterialization.attachedDatabaseName even when routed-query
support (dbConnectors?.runQuery) is absent, causing SQL to be qualified against
the wrong DB; change the logic that computes and uses finalDatabaseName so it
only uses state.db.config.coreMaterialization.attachedDatabaseName when
dbConnectors?.runQuery is truthy (otherwise leave finalDatabaseName undefined),
and apply the same guard to the other occurrences that qualify SQL (the nearby
blocks around the current assignment and the blocks at the other mentioned
sites) so fallback materialization continues to target db.currentDatabase when
routed queries are unavailable.
| app_project = (payload or {}).get("appProject") or {} | ||
| config = app_project.get("config") or {} | ||
| apps_by_sheet = config.get("appsBySheetId") or {} | ||
| exported = 0 | ||
| for sheet_id, sheet_app in apps_by_sheet.items(): |
There was a problem hiding this comment.
Validate nested payload object types before chained .get(...) / .items() access.
Line 83-86 can still crash with AttributeError if payload["appProject"], config, or appsBySheetId is not an object. Please fail gracefully (same pattern as Line 75-80) before using .get(...)/.items().
💡 Proposed fix
- app_project = (payload or {}).get("appProject") or {}
- config = app_project.get("config") or {}
- apps_by_sheet = config.get("appsBySheetId") or {}
+ app_project = payload.get("appProject") or {}
+ if not isinstance(app_project, dict):
+ typer.echo(
+ "Invalid payload_json format: appProject must be an object.",
+ err=True,
+ )
+ raise typer.Exit(code=1)
+ config = app_project.get("config") or {}
+ if not isinstance(config, dict):
+ typer.echo(
+ "Invalid payload_json format: appProject.config must be an object.",
+ err=True,
+ )
+ raise typer.Exit(code=1)
+ apps_by_sheet = config.get("appsBySheetId") or {}
+ if not isinstance(apps_by_sheet, dict):
+ typer.echo(
+ "Invalid payload_json format: config.appsBySheetId must be an object.",
+ err=True,
+ )
+ raise typer.Exit(code=1)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@python/sqlrooms-cli/sqlrooms/cli.py` around lines 82 - 86, The code assumes
payload["appProject"], config, and appsBySheetId are dicts before calling .get()
and .items(), which can raise AttributeError; before using app_project =
(payload or {}).get("appProject")..., validate types like in the earlier pattern
(lines ~75-80): check that payload is a dict and that app_project and config are
dicts (fallback to {} or return early) and ensure apps_by_sheet is a dict before
iterating; update the assignments for app_project, config, and apps_by_sheet and
guard the for sheet_id, sheet_app in apps_by_sheet.items() loop so it only runs
when apps_by_sheet is a mapping to avoid crashes in functions/variables named
payload, app_project, config, apps_by_sheet, exported and the loop over
sheet_id/sheet_app.
| target_rel = table_name.strip() | ||
|
|
||
| def _upload(cur): | ||
| import pyarrow as pa | ||
|
|
||
| reader = pa.ipc.open_stream(payload) | ||
| table = reader.read_all() | ||
| cur.register(tmp_rel, table) | ||
| try: | ||
| cur.execute( | ||
| f"CREATE OR REPLACE TABLE {target_rel} AS SELECT * FROM {tmp_rel_q}" | ||
| ) |
There was a problem hiding this comment.
SQL injection risk: target_rel is not quoted.
The target_rel variable (derived from user-supplied tableName) is interpolated directly into the SQL query without proper identifier quoting. While tmp_rel_q is correctly quoted, target_rel could allow an attacker to inject arbitrary SQL via a malicious table name.
🐛 Proposed fix
tmp_rel = "__sqlrooms_upload_" + str(query_id).replace("-", "_")
tmp_rel_q = _quote_ident(tmp_rel)
- target_rel = table_name.strip()
+ target_rel_q = _quote_ident(table_name.strip())
def _upload(cur):
import pyarrow as pa
reader = pa.ipc.open_stream(payload)
table = reader.read_all()
cur.register(tmp_rel, table)
try:
cur.execute(
- f"CREATE OR REPLACE TABLE {target_rel} AS SELECT * FROM {tmp_rel_q}"
+ f"CREATE OR REPLACE TABLE {target_rel_q} AS SELECT * FROM {tmp_rel_q}"
)
finally:📝 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.
| target_rel = table_name.strip() | |
| def _upload(cur): | |
| import pyarrow as pa | |
| reader = pa.ipc.open_stream(payload) | |
| table = reader.read_all() | |
| cur.register(tmp_rel, table) | |
| try: | |
| cur.execute( | |
| f"CREATE OR REPLACE TABLE {target_rel} AS SELECT * FROM {tmp_rel_q}" | |
| ) | |
| target_rel_q = _quote_ident(table_name.strip()) | |
| def _upload(cur): | |
| import pyarrow as pa | |
| reader = pa.ipc.open_stream(payload) | |
| table = reader.read_all() | |
| cur.register(tmp_rel, table) | |
| try: | |
| cur.execute( | |
| f"CREATE OR REPLACE TABLE {target_rel_q} AS SELECT * FROM {tmp_rel_q}" | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@python/sqlrooms-server/sqlrooms/server/server.py` around lines 116 - 127, The
code interpolates user-derived target_rel into SQL in _upload (cur.execute)
without quoting, enabling SQL injection; fix by validating and safely quoting
the identifier before use: in _upload, ensure target_rel (from table_name)
matches a strict pattern (e.g., /^[A-Za-z_][A-Za-z0-9_]*$/) or raise, then
construct a safe quoted identifier by escaping any double quotes (replace " with
"") and wrapping in double quotes, and use that safe identifier in the CREATE OR
REPLACE TABLE ... AS SELECT * FROM {tmp_rel_q} executed by cur.execute; keep
tmp_rel_q usage unchanged.
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
…tainer-package Signed-off-by: Ilya Boyandin <[email protected]>
Summary by CodeRabbit
New Features
Documentation