Skip to content

feat: Tanstack multi-room example - #385

Merged
ilyabo merged 8 commits into
mainfrom
cursor/development-environment-setup-f050
Feb 25, 2026
Merged

ilyabo merged 8 commits into
mainfrom
cursor/development-environment-setup-f050

Conversation

@ilyabo

@ilyabo ilyabo commented Feb 25, 2026 •

Copy link
Copy Markdown
Collaborator

Add Cursor Cloud specific instructions to AGENTS.md to document the development environment setup and usage.


Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added a multi-room example app: create/rename/delete rooms, per-room persisted state, and a table-browser UI for exploring room data.
  • Documentation

    • Added Cursor Cloud–specific contributing instructions (env setup, startup behavior, gotchas, example app notes).
    • Added multi-room example README and integrated the example into docs.

@cursor

cursor Bot commented Feb 25, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai

coderabbitai Bot commented Feb 25, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new "multi-room" example app (React + Vite + TypeScript + Tailwind) with persisted per-room stores, localStorage-backed room list CRUD and seeding, room detail pages with a table browser, and accompanying config, docs, and build tooling. No backend code changes.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md, docs/examples.md, examples/multi-room/README.md
Added Cursor Cloud-specific contributing notes, integrated the multi-room README into examples index, and created a README describing the example and usage.
Project config & entry
examples/multi-room/package.json, examples/multi-room/vite.config.ts, examples/multi-room/index.html, examples/multi-room/tsconfig.json, examples/multi-room/tsconfig.app.json, examples/multi-room/tsconfig.node.json
New example project config: Vite + React + Tailwind + TypeScript setup, scripts (dev/build/preview/lint), and TS project references.
App entry & routing
examples/multi-room/src/main.tsx
App bootstrapping, TanStack Router routes (root, index, /room/$id), router exposure via module augmentation, Toaster and header wiring, and seeding default rooms before mount.
Room list persistence
examples/multi-room/src/rooms-list.ts
Implements localStorage-backed RoomListEntry API: getRoomsList, getRoom, addRoom, renameRoom, deleteRoom, seedDefaultRooms; deletes per-room persisted keys on room removal.
Per-room store
examples/multi-room/src/room-store.ts
Creates a per-room persisted store factory (createRoomStore / useRoomStore) that initializes a room shell slice with provided default data sources and uses a per-room persistence key.
Pages & UI
examples/multi-room/src/RoomsListPage.tsx, examples/multi-room/src/RoomPage.tsx, examples/multi-room/src/main.tsx
Adds RoomsListPage (list, create, inline rename, delete, navigate) and RoomPage (RoomLoader, RoomShell, TableBrowser, QueryDataTable integration, loading/error states).
Styling & assets
examples/multi-room/src/index.css, examples/multi-room/src/vite-env.d.ts
Adds Tailwind/Tokens CSS, theme root variables, and Vite ambient types.
Example scaffolding
examples/multi-room/* (new README, package, src files, tsconfigs, vite config, index.html)
Overall new example folder with source, config, and documentation for the multi-room demo.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant Router as TanStack Router
  participant RoomsList as RoomsListPage
  participant RoomLoader as RoomLoader/RoomPage
  participant StoreFactory as createRoomStore
  participant Storage as localStorage
  participant DuckDB as DuckDB WASM

  Browser->>Router: Navigate to "/" or "/room/{id}"
  Router->>RoomsList: Render list (if "/")
  RoomsList->>Storage: getRoomsList()
  Storage-->>RoomsList: rooms[]
  RoomsList->>Browser: User selects or creates room
  Browser->>Router: Navigate to "/room/{id}"
  Router->>RoomLoader: Render RoomLoader for id
  RoomLoader->>Storage: getRoom(id)
  Storage-->>RoomLoader: RoomListEntry (dataSources)
  RoomLoader->>StoreFactory: createRoomStore(id, dataSources)
  StoreFactory->>Storage: init per-room persist key (multi-room-{id})
  StoreFactory->>DuckDB: instantiate/attach DuckDB WASM per-room
  DuckDB-->>StoreFactory: ready
  StoreFactory-->>RoomLoader: store ready
  RoomLoader->>Browser: render RoomShell + TableBrowser with data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped into rooms, one, two, and three,

Each with its own store and a tiny DB,
Tables to peek and names to rename,
Seeded and saved, every room has a name,
Hooray for the demo—let curiosity reign! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Tanstack multi-room example' accurately describes the main change: adding a new multi-room example application using TanStack Router. The title is concise, specific, and clearly reflects the primary purpose of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/development-environment-setup-f050

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.

❤️ Share

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

@ilyabo ilyabo changed the title Development environment setup docs: Development environment setup Feb 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AGENTS.md`:
- Line 41: The heading "Cursor Cloud specific instructions" should use a
hyphenated compound modifier; update the heading text (the line containing
"Cursor Cloud specific instructions") to "Cursor-Cloud-specific instructions" so
the compound modifier correctly links to "specific instructions."

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebbb4e and fa95758.

📒 Files selected for processing (1)
  • AGENTS.md

Comment thread AGENTS.md

When adding features, update the relevant documentation and verify that example applications work with your changes. See `CONTRIBUTING.md` for full guidelines.

## Cursor Cloud specific instructions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use hyphenated compound modifier in heading.

Line 41 reads a bit awkwardly; use a hyphenated form for the modifier.

✏️ Suggested edit
-## Cursor Cloud specific instructions
+## Cursor Cloud-specific instructions
📝 Committable suggestion

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

Suggested change
## Cursor Cloud specific instructions
## Cursor Cloud-specific instructions
🧰 Tools
🪛 LanguageTool

[grammar] ~41-~41: Use a hyphen to join words.
Context: ...d` for full guidelines. ## Cursor Cloud specific instructions ### Environment ...

(QB_NEW_EN_HYPHEN)

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

In `@AGENTS.md` at line 41, The heading "Cursor Cloud specific instructions"
should use a hyphenated compound modifier; update the heading text (the line
containing "Cursor Cloud specific instructions") to "Cursor-Cloud-specific
instructions" so the compound modifier correctly links to "specific
instructions."

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (1)
examples/multi-room/src/rooms-db.ts (1)

111-113: Seed defaults in one transaction instead of per-room DB open/close.

The current loop calls putRoom() for each default, which repeatedly opens/closes IndexedDB connections. A single readwrite transaction is lighter and more robust.

Suggested refactor
 export async function seedDefaultRooms(): Promise<void> {
   const existing = await getAllRooms();
   if (existing.length > 0) return;
@@
-  for (const room of defaults) {
-    await putRoom(room);
-  }
+  const db = await openDb();
+  try {
+    const tx = db.transaction(STORE_NAME, 'readwrite');
+    const store = tx.objectStore(STORE_NAME);
+    for (const room of defaults) {
+      store.put(room);
+    }
+    await new Promise<void>((resolve, reject) => {
+      tx.oncomplete = () => resolve();
+      tx.onerror = () => reject(tx.error);
+      tx.onabort = () => reject(tx.error);
+    });
+  } finally {
+    db.close();
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/rooms-db.ts` around lines 111 - 113, The seed routine
currently calls putRoom() inside a loop which opens/closes the IndexedDB
connection per room; instead, open the DB once and perform all writes in a
single readwrite transaction to be efficient and atomic. Modify the seeding code
so it obtains the same DB instance used by putRoom (or refactor putRoom to
accept an existing IDBTransaction/IDBObjectStore), create a single transaction
('readwrite') and call objectStore.put(...) for each room in defaults, then
await the transaction completion before closing; update references to putRoom,
the DB-opening function, and the transaction handling to ensure all default
rooms are seeded in that single transaction.
🤖 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/multi-room/package.json`:
- Line 31: Update the TypeScript dependency in examples/multi-room's
package.json by changing the pinned version of "typescript" from "5.9.2" to the
workspace baseline "5.9.3" so the project uses the repo-wide TypeScript version
and avoids inconsistent typechecking; ensure the package.json entry for
"typescript" is updated accordingly and run a quick npm/yarn install and
typecheck to confirm compatibility.

In `@examples/multi-room/src/index.css`:
- Around line 5-9: The Tailwind v4 `@source` directives in
examples/multi-room/src/index.css are being flagged by linting; update the
Stylelint config to allow the "source" at-rule by adding an at-rule-no-unknown
exception (e.g., set "at-rule-no-unknown": [true, { "ignoreAtRules": ["source"]
}]) in .stylelintrc.json, and update biome.json's CSS lint/parser options to
accept unknown Tailwind at-rules (e.g., add an allow/ignore entry for "source"
in the CSS lint rules or parser options) so the `@source` directives are not
reported as unknown.

In `@examples/multi-room/src/main.tsx`:
- Around line 56-62: Currently the app mount is nested inside
seedDefaultRooms().then(...), which prevents rendering if seeding fails; instead
call
createRoot(document.getElementById('root')!).render(<StrictMode><RouterProvider
router={router} /></StrictMode>) immediately to mount the app, and run
seedDefaultRooms() separately in the background (e.g.,
seedDefaultRooms().catch(err => /* log/handle error */) or use async IIFE) so
seeding failures are handled without blocking createRoot/render; update
references to seedDefaultRooms and the createRoot/render block accordingly.

In `@examples/multi-room/src/room-store.ts`:
- Around line 17-29: The store currently runs room.initialize() immediately via
an async IIFE inside room-store.ts (calling store.getState().room.initialize(),
then store.setState(... draft.room.initialized = true) and catching errors with
store.getState().room.captureException), which creates a race with the
component; remove that IIFE from the store factory entirely and instead call
await room.initialize() from the RoomPage component's useEffect, then set the
component's ready state only after initialization completes and update the
store's initialized flag via the existing store.setState code; keep the existing
error handling by calling room.captureException(error) from the component if
initialize() throws.

In `@examples/multi-room/src/RoomPage.tsx`:
- Around line 20-26: The useEffect call that invokes getRoom(roomId) doesn't
handle promise rejection, which can leave the spinner indefinitely; update the
effect to handle errors by adding a .catch (or use an async function with
try/catch) on the getRoom(roomId) call, and in the catch branch set an
appropriate state (e.g., call setNotFound(true) or an error state) while still
respecting the cancelled flag before updating state; ensure the promise chain
also ignores updates when cancelled is true so setRoomRecord or setNotFound are
only called when not cancelled.
- Around line 173-176: selectedTable is interpolated directly into the SQL
string passed to QueryDataTable (query={`SELECT * FROM "${selectedTable}"`})
which breaks if the table name contains double-quotes; fix by escaping any
double-quote characters in selectedTable before composing the query (e.g.,
replace all " with ""), or add a small helper like quoteIdentifier(name) that
returns the identifier safely quoted, and use that helper when building the
query for QueryDataTable.

In `@examples/multi-room/src/RoomsListPage.tsx`:
- Around line 43-51: confirmRename is being called from both onBlur and onSubmit
which can cause duplicate putRoom() writes; add a simple guard (e.g., component
state boolean like isSaving or isSubmitting) and check it at the top of
confirmRename (return early if true), set it true immediately before calling
await putRoom({...}) and clear it after the await (in finally), and ensure the
same guard is applied to the other rename handler that also calls putRoom so
blur+submit cannot run concurrently.
- Around line 17-23: The mount logic currently calls loadRooms() which awaits
getAllRooms() without handling rejections; update loadRooms (and the useEffect
caller) to handle errors: wrap the await getAllRooms() call inside try/catch in
the loadRooms function (or call loadRooms().catch(...) in the effect), on error
set an error/loading state (e.g., setError, setLoading) and optionally clear or
preserve rooms (e.g., setRooms([]) or leave previous value) and surface the
error to the UI (toast, message, or retry button) so failures no longer silently
leave the page stale; reference functions/useEffect: loadRooms, getAllRooms,
setRooms, and the useEffect that invokes loadRooms.

In `@examples/multi-room/vite.config.ts`:
- Around line 5-7: Add "zod" to examples/multi-room/package.json dependencies
and validate the exported Vite config using a Zod schema: define a ConfigSchema
(e.g., const ConfigSchema = z.object({ plugins: z.array(z.any()) })) and infer
its type via z.infer<typeof ConfigSchema>, parse/validate the object you pass to
defineConfig (the default export) with ConfigSchema.parse(...) before returning
it; update the exported symbol (the object passed to defineConfig in the default
export) to use the validated/typed value so the repo's config-validation rule is
satisfied.

---

Nitpick comments:
In `@examples/multi-room/src/rooms-db.ts`:
- Around line 111-113: The seed routine currently calls putRoom() inside a loop
which opens/closes the IndexedDB connection per room; instead, open the DB once
and perform all writes in a single readwrite transaction to be efficient and
atomic. Modify the seeding code so it obtains the same DB instance used by
putRoom (or refactor putRoom to accept an existing
IDBTransaction/IDBObjectStore), create a single transaction ('readwrite') and
call objectStore.put(...) for each room in defaults, then await the transaction
completion before closing; update references to putRoom, the DB-opening
function, and the transaction handling to ensure all default rooms are seeded in
that single transaction.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa95758 and d57e37a.

📒 Files selected for processing (12)
  • examples/multi-room/index.html
  • examples/multi-room/package.json
  • examples/multi-room/src/RoomPage.tsx
  • examples/multi-room/src/RoomsListPage.tsx
  • examples/multi-room/src/index.css
  • examples/multi-room/src/main.tsx
  • examples/multi-room/src/room-store.ts
  • examples/multi-room/src/rooms-db.ts
  • examples/multi-room/tsconfig.app.json
  • examples/multi-room/tsconfig.json
  • examples/multi-room/tsconfig.node.json
  • examples/multi-room/vite.config.ts
✅ Files skipped from review due to trivial changes (1)
  • examples/multi-room/index.html

"@vitejs/plugin-react": "^5.0.3",
"eslint": "^9.36.0",
"tailwindcss": "^4.1.18",
"typescript": "5.9.2",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the specific file mentioned in the review
cat -n examples/multi-room/package.json

Repository: sqlrooms/sqlrooms

Length of output: 1172


🏁 Script executed:

#!/bin/bash
# Run the verification script from the review to check all packages
python - <<'PY'
import json, pathlib
expected = "5.9.3"
for p in sorted(pathlib.Path(".").rglob("package.json")):
    try:
        data = json.loads(p.read_text())
    except Exception:
        continue
    ts = (data.get("devDependencies", {}) or {}).get("typescript") or (data.get("dependencies", {}) or {}).get("typescript")
    if ts:
        status = "OK" if ts == expected else "MISMATCH"
        print(f"{status}: {p} -> {ts}")
PY

Repository: sqlrooms/sqlrooms

Length of output: 1411


Align TypeScript to the workspace baseline.

The package pins typescript to 5.9.2, which drifts from the repo-wide baseline of 5.9.3 and can produce inconsistent typecheck behavior.

🔧 Proposed fix
-    "typescript": "5.9.2",
+    "typescript": "5.9.3",
📝 Committable suggestion

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

Suggested change
"typescript": "5.9.2",
"typescript": "5.9.3",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/package.json` at line 31, Update the TypeScript
dependency in examples/multi-room's package.json by changing the pinned version
of "typescript" from "5.9.2" to the workspace baseline "5.9.3" so the project
uses the repo-wide TypeScript version and avoids inconsistent typechecking;
ensure the package.json entry for "typescript" is updated accordingly and run a
quick npm/yarn install and typecheck to confirm compatibility.

Comment on lines +5 to +9
@source '../index.html';
@source 'src/**/*.{ts,tsx}';
@source '../../../packages/*/src/**/*.{ts,tsx}';
@source '{./,../../../}node_modules/@sqlrooms/*/dist/**/*.js';
@source '{./,../../../}node_modules/.pnpm/node_modules/@sqlrooms/*/dist/**/*.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Tailwind directives in this file ==="
rg -n '@source|@import' examples/multi-room/src/index.css

echo
echo "=== Stylelint config entries related to unknown at-rules ==="
fd -HI 'stylelint.config.*|.stylelintrc.*' -a | xargs -r rg -n 'at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|source'

echo
echo "=== Biome config entries related to CSS parsing ==="
fd -HI 'biome.json|biome.jsonc' -a | xargs -r rg -n 'css|tailwind|parser'

Repository: sqlrooms/sqlrooms

Length of output: 494


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== All config files in repo ==="
fd -HI 'stylelint|biome|eslint|prettier' --type f -e json -e js -e jsonc -e yaml -e yml -e config | head -30

echo
echo "=== Checking package.json for linting deps ==="
find . -name "package.json" -type f | xargs grep -l "stylelint\|biome" | head -10

echo
echo "=== Root level config files ==="
ls -la | grep -E 'stylelint|biome|eslint|prettier' || echo "No root level linting configs found"

echo
echo "=== Examples/multi-room package.json ==="
cat examples/multi-room/package.json 2>/dev/null || echo "File not found"

Repository: sqlrooms/sqlrooms

Length of output: 1143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .stylelintrc.json content ==="
cat .stylelintrc.json

echo
echo "=== biome.json content ==="
cat biome.json

echo
echo "=== examples/multi-room/package.json ==="
cat examples/multi-room/package.json 2>/dev/null || echo "File not found"

Repository: sqlrooms/sqlrooms

Length of output: 3818


Configure lint tooling for Tailwind v4 @source directives.

The @source directives in lines 5-9 are valid Tailwind v4 syntax, but the Stylelint and Biome configurations do not explicitly allow them. The .stylelintrc.json extends stylelint-config-standard-scss without disabling or configuring the at-rule-no-unknown rule, and biome.json has CSS linting enabled with default rules that will flag @source as unknown. Add explicit configuration (e.g., ignoreAtRules in Stylelint or CSS parser options in Biome) to allow these Tailwind directives.

🧰 Tools
🪛 Biome (2.4.4)

[error] 5-5: Tailwind-specific syntax is disabled.

(parse)


[error] 6-6: Tailwind-specific syntax is disabled.

(parse)


[error] 7-7: Tailwind-specific syntax is disabled.

(parse)


[error] 8-8: Tailwind-specific syntax is disabled.

(parse)


[error] 9-9: Tailwind-specific syntax is disabled.

(parse)

🪛 Stylelint (17.3.0)

[error] 5-5: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 6-6: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 7-7: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 8-8: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 9-9: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

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

In `@examples/multi-room/src/index.css` around lines 5 - 9, The Tailwind v4
`@source` directives in examples/multi-room/src/index.css are being flagged by
linting; update the Stylelint config to allow the "source" at-rule by adding an
at-rule-no-unknown exception (e.g., set "at-rule-no-unknown": [true, {
"ignoreAtRules": ["source"] }]) in .stylelintrc.json, and update biome.json's
CSS lint/parser options to accept unknown Tailwind at-rules (e.g., add an
allow/ignore entry for "source" in the CSS lint rules or parser options) so the
`@source` directives are not reported as unknown.

Comment thread examples/multi-room/src/main.tsx Outdated
Comment thread examples/multi-room/src/room-store.ts Outdated
Comment thread examples/multi-room/src/RoomPage.tsx Outdated
Comment on lines +20 to +26
useEffect(() => {
let cancelled = false;
getRoom(roomId).then((record) => {
if (cancelled) return;
if (record) setRoomRecord(record);
else setNotFound(true);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle getRoom rejection to avoid indefinite loading state.

If getRoom(roomId) rejects, neither roomRecord nor notFound updates, so the spinner can render forever and the rejection is unhandled.

Suggested fix
   useEffect(() => {
     let cancelled = false;
-    getRoom(roomId).then((record) => {
-      if (cancelled) return;
-      if (record) setRoomRecord(record);
-      else setNotFound(true);
-    });
+    getRoom(roomId)
+      .then((record) => {
+        if (cancelled) return;
+        if (record) setRoomRecord(record);
+        else setNotFound(true);
+      })
+      .catch((error) => {
+        if (cancelled) return;
+        console.error('Failed to load room:', error);
+        setNotFound(true);
+      });
     return () => {
       cancelled = true;
     };
   }, [roomId]);
📝 Committable suggestion

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

Suggested change
useEffect(() => {
let cancelled = false;
getRoom(roomId).then((record) => {
if (cancelled) return;
if (record) setRoomRecord(record);
else setNotFound(true);
});
useEffect(() => {
let cancelled = false;
getRoom(roomId)
.then((record) => {
if (cancelled) return;
if (record) setRoomRecord(record);
else setNotFound(true);
})
.catch((error) => {
if (cancelled) return;
console.error('Failed to load room:', error);
setNotFound(true);
});
return () => {
cancelled = true;
};
}, [roomId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/RoomPage.tsx` around lines 20 - 26, The useEffect
call that invokes getRoom(roomId) doesn't handle promise rejection, which can
leave the spinner indefinitely; update the effect to handle errors by adding a
.catch (or use an async function with try/catch) on the getRoom(roomId) call,
and in the catch branch set an appropriate state (e.g., call setNotFound(true)
or an error state) while still respecting the cancelled flag before updating
state; ensure the promise chain also ignores updates when cancelled is true so
setRoomRecord or setNotFound are only called when not cancelled.

Comment on lines +173 to +176
<QueryDataTable
key={selectedTable}
query={`SELECT * FROM "${selectedTable}"`}
fontSize="text-xs"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Escape table identifiers before composing SQL text.

selectedTable is interpolated directly into the query. If a table name contains ", this can break the query and produce unintended SQL text.

Suggested fix
 function TableBrowser() {
   const store = useRoomStoreApi<RoomState>();
   const initialized = useStore(store, (s) => s.room.initialized);
   const tables = useStore(store, (s) => s.db.tables);
   const [selectedTable, setSelectedTable] = useState<string | null>(null);
+  const quoteIdentifier = (name: string) => `"${name.replace(/"/g, '""')}"`;
@@
         {selectedTable ? (
           <QueryDataTable
             key={selectedTable}
-            query={`SELECT * FROM "${selectedTable}"`}
+            query={`SELECT * FROM ${quoteIdentifier(selectedTable)}`}
             fontSize="text-xs"
           />
         ) : (
📝 Committable suggestion

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

Suggested change
<QueryDataTable
key={selectedTable}
query={`SELECT * FROM "${selectedTable}"`}
fontSize="text-xs"
<QueryDataTable
key={selectedTable}
query={`SELECT * FROM ${quoteIdentifier(selectedTable)}`}
fontSize="text-xs"
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/RoomPage.tsx` around lines 173 - 176, selectedTable
is interpolated directly into the SQL string passed to QueryDataTable
(query={`SELECT * FROM "${selectedTable}"`}) which breaks if the table name
contains double-quotes; fix by escaping any double-quote characters in
selectedTable before composing the query (e.g., replace all " with ""), or add a
small helper like quoteIdentifier(name) that returns the identifier safely
quoted, and use that helper when building the query for QueryDataTable.

Comment on lines +17 to +23
const loadRooms = useCallback(async () => {
setRooms(await getAllRooms());
}, []);

useEffect(() => {
loadRooms();
}, [loadRooms]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle room-list load failures so mount loading doesn’t silently fail.

If getAllRooms() rejects, the promise from loadRooms() is currently unhandled in the mount effect, which can leave the page stale without recovery.

Suggested fix
   const loadRooms = useCallback(async () => {
-    setRooms(await getAllRooms());
+    try {
+      setRooms(await getAllRooms());
+    } catch (error) {
+      console.error('Failed to load rooms:', error);
+    }
   }, []);

   useEffect(() => {
-    loadRooms();
+    void loadRooms();
   }, [loadRooms]);
📝 Committable suggestion

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

Suggested change
const loadRooms = useCallback(async () => {
setRooms(await getAllRooms());
}, []);
useEffect(() => {
loadRooms();
}, [loadRooms]);
const loadRooms = useCallback(async () => {
try {
setRooms(await getAllRooms());
} catch (error) {
console.error('Failed to load rooms:', error);
}
}, []);
useEffect(() => {
void loadRooms();
}, [loadRooms]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/RoomsListPage.tsx` around lines 17 - 23, The mount
logic currently calls loadRooms() which awaits getAllRooms() without handling
rejections; update loadRooms (and the useEffect caller) to handle errors: wrap
the await getAllRooms() call inside try/catch in the loadRooms function (or call
loadRooms().catch(...) in the effect), on error set an error/loading state
(e.g., setError, setLoading) and optionally clear or preserve rooms (e.g.,
setRooms([]) or leave previous value) and surface the error to the UI (toast,
message, or retry button) so failures no longer silently leave the page stale;
reference functions/useEffect: loadRooms, getAllRooms, setRooms, and the
useEffect that invokes loadRooms.

Comment on lines +43 to +51
const confirmRename = async () => {
if (!editingId) return;
const room = rooms.find((r) => r.id === editingId);
if (room && editName.trim()) {
await putRoom({...room, name: editName.trim()});
await loadRooms();
}
setEditingId(null);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prevent duplicate rename writes from blur + submit firing together.

The same async mutator runs from both onBlur and onSubmit. Clicking Save can trigger both paths and issue duplicate putRoom() calls.

Suggested fix
-import {useCallback, useEffect, useState} from 'react';
+import {useCallback, useEffect, useRef, useState} from 'react';
@@
   const [rooms, setRooms] = useState<RoomRecord[]>([]);
   const [editingId, setEditingId] = useState<string | null>(null);
   const [editName, setEditName] = useState('');
+  const renameInFlightRef = useRef(false);
@@
   const confirmRename = async () => {
-    if (!editingId) return;
-    const room = rooms.find((r) => r.id === editingId);
-    if (room && editName.trim()) {
-      await putRoom({...room, name: editName.trim()});
-      await loadRooms();
+    if (!editingId || renameInFlightRef.current) return;
+    renameInFlightRef.current = true;
+    try {
+      const room = rooms.find((r) => r.id === editingId);
+      if (room && editName.trim()) {
+        await putRoom({...room, name: editName.trim()});
+        await loadRooms();
+      }
+      setEditingId(null);
+    } finally {
+      renameInFlightRef.current = false;
     }
-    setEditingId(null);
   };

Also applies to: 80-90

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

In `@examples/multi-room/src/RoomsListPage.tsx` around lines 43 - 51,
confirmRename is being called from both onBlur and onSubmit which can cause
duplicate putRoom() writes; add a simple guard (e.g., component state boolean
like isSaving or isSubmitting) and check it at the top of confirmRename (return
early if true), set it true immediately before calling await putRoom({...}) and
clear it after the await (in finally), and ensure the same guard is applied to
the other rename handler that also calls putRoom so blur+submit cannot run
concurrently.

Comment on lines +5 to +7
export default defineConfig({
plugins: [react(), tailwindcss()],
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if Zod is in project dependencies
echo "=== Checking for Zod in workspace ==="
find . -name "package.json" -type f | head -20 | xargs -I {} sh -c 'echo "File: {}"; cat "{}" | grep -A2 -B2 "zod" || true'

echo ""
echo "=== Looking for all config files in the project ==="
fd -e "config.ts" -e "config.tsx" -e "config.js" | head -20

Repository: sqlrooms/sqlrooms

Length of output: 3251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if vite.config.ts exists and read it
echo "=== Current vite.config.ts content ==="
if [ -f "examples/multi-room/vite.config.ts" ]; then
  cat -n "examples/multi-room/vite.config.ts"
else
  echo "File not found at examples/multi-room/vite.config.ts"
  # Search for it
  fd "vite.config.ts"
fi

echo ""
echo "=== Checking root package.json for Zod ==="
if [ -f "package.json" ]; then
  cat "package.json" | jq '.dependencies, .devDependencies' 2>/dev/null | grep -A5 -B5 "zod" || echo "Zod not found in dependencies"
fi

Repository: sqlrooms/sqlrooms

Length of output: 709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check multi-room package.json
echo "=== examples/multi-room/package.json ==="
if [ -f "examples/multi-room/package.json" ]; then
  cat "examples/multi-room/package.json" | jq '.dependencies, .devDependencies' 2>/dev/null
else
  echo "File not found"
fi

echo ""
echo "=== Checking a few other vite.config.ts files ==="
for file in "apps/sqlrooms-cli-ui/vite.config.ts" "examples/ai/vite.config.ts" "examples/app-builder/vite.config.ts"; do
  if [ -f "$file" ]; then
    echo "--- $file ---"
    cat -n "$file" | head -15
    echo ""
  fi
done

Repository: sqlrooms/sqlrooms

Length of output: 2144


Add Zod to package.json and validate the config object using a Zod schema.

This file violates the repository config-validation rule. The proposed refactor requires Zod to be added to examples/multi-room/package.json as a dependency:

{
  "dependencies": {
    "zod": "^4.1.8"
  }
}

Then apply the validation pattern:

🔧 Proposed refactor
 import {defineConfig} from 'vite';
 import tailwindcss from '@tailwindcss/vite';
 import react from '@vitejs/plugin-react';
+import {z} from 'zod';
 
+const ViteConfigSchema = z.object({
+  plugins: z.array(z.any()),
+});
+
+type ViteConfig = z.infer<typeof ViteConfigSchema>;
+
+const config: ViteConfig = {
+  plugins: [react(), tailwindcss()],
+};
+
 export default defineConfig({
-  plugins: [react(), tailwindcss()],
+  ...ViteConfigSchema.parse(config),
 });

As per coding guidelines: "Validate all configuration objects using Zod schemas and infer types via z.infer<typeof ConfigSchema>".

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

In `@examples/multi-room/vite.config.ts` around lines 5 - 7, Add "zod" to
examples/multi-room/package.json dependencies and validate the exported Vite
config using a Zod schema: define a ConfigSchema (e.g., const ConfigSchema =
z.object({ plugins: z.array(z.any()) })) and infer its type via z.infer<typeof
ConfigSchema>, parse/validate the object you pass to defineConfig (the default
export) with ConfigSchema.parse(...) before returning it; update the exported
symbol (the object passed to defineConfig in the default export) to use the
validated/typed value so the repo's config-validation rule is satisfied.

@ilyabo ilyabo changed the title docs: Development environment setup feat: Tanstack multi-room example Feb 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/multi-room/README.md`:
- Line 3: Update the display text "Github repo" to use the correct
capitalization "GitHub" in the README entry (the link label in the
examples/multi-room README where the bracketed text reads "Github repo"); keep
the URL unchanged and only modify the visible link text to "GitHub repo".
- Line 5: Remove the invalid alt attribute from the <video> element and provide
accessible alternatives: either add descriptive fallback text (or a link)
between the opening and closing <video> tags, or include an appropriate
aria-label/aria-labelledby on the <video> element to convey the same
description; update the <video> tag in the README so browsers no longer use an
ignored alt attribute and screen readers receive the descriptive text.
- Line 5: Remove the invalid alt attribute from the <video> element and instead
add accessible fallback content between the opening and closing <video> tags
(e.g., a short descriptive text, captions, or a link to the media) so assistive
technologies and browsers without video support can present a meaningful
alternative; update the existing <video src="/media/examples/multi-room.mp4"
width="450" controls loop muted> element accordingly.
- Around line 19-21: Update the README instruction to specify that pnpm install
must be run from the repository root (so workspace:* dependencies resolve) and
change the dev command to include the workspace filter for the example (use pnpm
dev --filter sqlrooms-multi-room); ensure the README shows a comment like "From
the repo root" before the install command and replace the plain "pnpm dev" line
with "pnpm dev --filter sqlrooms-multi-room" so users run the example correctly
in the monorepo context.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d57e37a and e96f0e7.

⛔ Files ignored due to path filters (2)
  • docs/media/examples/multi-room.mp4 is excluded by !**/*.mp4
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • docs/examples.md
  • examples/multi-room/README.md

@@ -0,0 +1,22 @@
### Multi-Room

[Github repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Github → GitHub (capital "H")

The official name is GitHub.

✏️ Proposed fix
-[Github repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)
+[GitHub repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)
📝 Committable suggestion

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

Suggested change
[Github repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)
[GitHub repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)
🧰 Tools
🪛 LanguageTool

[uncategorized] ~3-~3: The official name of this software platform is spelled with a capital “H”.
Context: ### Multi-Room [Github repo](https://github.com/sqlrooms/sqlro...

(GITHUB)

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

In `@examples/multi-room/README.md` at line 3, Update the display text "Github
repo" to use the correct capitalization "GitHub" in the README entry (the link
label in the examples/multi-room README where the bracketed text reads "Github
repo"); keep the URL unchanged and only modify the visible link text to "GitHub
repo".


[Github repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)

<video src="/media/examples/multi-room.mp4" alt="SQLRooms Multi-Room example" width="450" controls loop muted></video>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

alt is not a valid attribute on <video>

The HTML5 specification intentionally omits an alt attribute for <video> — it cannot be used within the opening <video> tag; this is by design as the video element can contain fallback options. The attribute is silently ignored by browsers. Use inline fallback text between the tags, or aria-label for an accessible label.

♿ Proposed fix
-<video src="/media/examples/multi-room.mp4" alt="SQLRooms Multi-Room example" width="450" controls loop muted></video>
+<video src="/media/examples/multi-room.mp4" aria-label="SQLRooms Multi-Room example" width="450" controls loop muted>
+  SQLRooms Multi-Room example (video)
+</video>
📝 Committable suggestion

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

Suggested change
<video src="/media/examples/multi-room.mp4" alt="SQLRooms Multi-Room example" width="450" controls loop muted></video>
<video src="/media/examples/multi-room.mp4" aria-label="SQLRooms Multi-Room example" width="450" controls loop muted>
SQLRooms Multi-Room example (video)
</video>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/README.md` at line 5, Remove the invalid alt attribute
from the <video> element and provide accessible alternatives: either add
descriptive fallback text (or a link) between the opening and closing <video>
tags, or include an appropriate aria-label/aria-labelledby on the <video>
element to convey the same description; update the <video> tag in the README so
browsers no longer use an ignored alt attribute and screen readers receive the
descriptive text.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

HTML5 alt attribute video element specification validity

💡 Result:

The HTML (Living Standard) <video> element does not define an alt attribute, so alt on <video> is not spec-valid (a validator should flag it as an invalid attribute). The only attributes defined for <video> are global attributes plus media/video-specific ones like src, poster, preload, autoplay, loop, muted, controls, width, height, etc.—not alt. [1]

What to do instead (spec-valid patterns):

  • Put a text alternative / download links as fallback content inside <video>…</video> (shown in MDN examples). [2]
  • Provide accessibility alternatives appropriate to video: captions/subtitles (and usually a transcript / audio description depending on content). [3]
  • If you need an accessible name for the control, use ARIA labeling (e.g., aria-label) rather than alt (ARIA is allowed via global attributes). [1]

Also, the poster image is configured via the poster attribute, and the spec historically did not allow alt on <video> as a way to describe either the video or its poster. [1] [4]

Sources: [1] WHATWG HTML Standard – media elements (<video> attributes) (html.spec.whatwg.org); [2] MDN <source> / <video> example showing fallback content inside <video> (developer.mozilla.org); [3] W3C WAI guidance on captions requirements/needs (w3.org); [4] W3C HTML WG wiki (historical change proposal noting alt not allowed on <video>) (w3.org).

Citations:


🏁 Script executed:

cat examples/multi-room/README.md | head -20

Repository: sqlrooms/sqlrooms

Length of output: 897


Remove invalid alt attribute from <video> element

The alt attribute is not defined in the HTML5 specification for <video> elements and will be silently ignored by browsers. Place accessible fallback content (descriptive text, captions, or a link) between the opening and closing <video> tags instead.

Proposed fix
-<video src="/media/examples/multi-room.mp4" alt="SQLRooms Multi-Room example" width="450" controls loop muted></video>
+<video src="/media/examples/multi-room.mp4" width="450" controls loop muted>
+  SQLRooms Multi-Room example (video)
+</video>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/README.md` at line 5, Remove the invalid alt attribute
from the <video> element and instead add accessible fallback content between the
opening and closing <video> tags (e.g., a short descriptive text, captions, or a
link to the media) so assistive technologies and browsers without video support
can present a meaningful alternative; update the existing <video
src="/media/examples/multi-room.mp4" width="450" controls loop muted> element
accordingly.

Comment on lines +19 to +21
```sh
pnpm install
pnpm dev

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm workspace setup and multi-room package name
echo "=== Checking pnpm-workspace.yaml ===" 
cat pnpm-workspace.yaml 2>/dev/null || echo "No pnpm-workspace.yaml found"

echo -e "\n=== Checking multi-room package.json ==="
fd "package.json" examples/multi-room --max-depth 1 --exec cat {}

echo -e "\n=== Checking current README.md at lines 15-25 for context ===" 
sed -n '15,25p' examples/multi-room/README.md

Repository: sqlrooms/sqlrooms

Length of output: 1319


Clarify where pnpm install should be run in monorepo context

The README doesn't specify that pnpm install must run from the repo root. In this workspace with workspace:* protocol dependencies, running from the example subdirectory will fail to resolve internal packages correctly.

✏️ Proposed fix
 #### Running locally
 
 ```sh
+# From the repo root
 pnpm install
-pnpm dev
+pnpm dev --filter sqlrooms-multi-room
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @examples/multi-room/README.md around lines 19 - 21, Update the README
instruction to specify that pnpm install must be run from the repository root
(so workspace:* dependencies resolve) and change the dev command to include the
workspace filter for the example (use pnpm dev --filter sqlrooms-multi-room);
ensure the README shows a comment like "From the repo root" before the install
command and replace the plain "pnpm dev" line with "pnpm dev --filter
sqlrooms-multi-room" so users run the example correctly in the monorepo context.


</details>

<!-- fingerprinting:phantom:triton:churro -->

<!-- This is an auto-generated comment by CodeRabbit -->

…Configs, localStorage

- Replace IndexedDB with localStorage for room list + persistSliceConfigs per room
- Use createRoomStoreCreator() with roomId parameter and storeKey for lifecycle
- Use useRoomStore from factory in all components
- Rely on sqlrooms built-in store init/destroy via storeKey mechanism
- Clean up persist storage on room deletion
- Fix StrictMode compatibility by not manually calling destroy

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
examples/multi-room/src/RoomPage.tsx (1)

123-126: ⚠️ Potential issue | 🟡 Minor

Escape table identifiers before composing SQL text.

selectedTable is interpolated directly into SQL. Table names containing " can break the query text.

Proposed fix
 function TableBrowser() {
   const initialized = useRoomStore((s) => s.room.initialized);
   const tables = useRoomStore((s) => s.db.tables);
   const [selectedTable, setSelectedTable] = useState<string | null>(null);
+  const quoteIdentifier = (name: string) => `"${name.replace(/"/g, '""')}"`;
@@
         {selectedTable ? (
           <QueryDataTable
             key={selectedTable}
-            query={`SELECT * FROM "${selectedTable}"`}
+            query={`SELECT * FROM ${quoteIdentifier(selectedTable)}`}
             fontSize="text-xs"
           />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/RoomPage.tsx` around lines 123 - 126, The SQL is
built by interpolating selectedTable directly into the query prop of
QueryDataTable, which allows table names containing double quotes to break the
SQL; before composing the query string in RoomPage (where selectedTable is
used), escape the identifier by replacing any " characters with "" and then wrap
the result in double quotes (i.e., produce a safe identifier) so the query prop
receives a properly quoted table name; update the code that sets query={`SELECT
* FROM "${selectedTable}"`} to use the escaped identifier logic for
selectedTable before passing it into QueryDataTable.
🤖 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/multi-room/src/RoomPage.tsx`:
- Line 17: The useRef call for storeRef is invalid under React 19 because useRef
requires an initial value; update the declaration of storeRef (currently
useRef<StoreApi<RoomState>>()) to supply an initial value and include null in
the type to match nullable ref semantics (e.g., useRef<StoreApi<RoomState> |
null>(null)), so the ref is properly typed and initialized for RoomPage
components that reference StoreApi<RoomState>.

In `@examples/multi-room/src/rooms-list.ts`:
- Around line 28-30: addRoom currently appends a new RoomListEntry without
checking for duplicate id, causing getRoom to return a stale first match and
deleteRoom to remove all matches; update addRoom to guard against duplicates by
using getRoomsList() to check for an existing entry with the same id
(RoomListEntry.id) and either early-return if found or replace/deduplicate prior
to calling saveRoomsList([...]) so the saved list contains at most one entry per
id; reference addRoom, getRoomsList, saveRoomsList, getRoom, deleteRoom, and
RoomListEntry.id when making the change.

---

Duplicate comments:
In `@examples/multi-room/src/RoomPage.tsx`:
- Around line 123-126: The SQL is built by interpolating selectedTable directly
into the query prop of QueryDataTable, which allows table names containing
double quotes to break the SQL; before composing the query string in RoomPage
(where selectedTable is used), escape the identifier by replacing any "
characters with "" and then wrap the result in double quotes (i.e., produce a
safe identifier) so the query prop receives a properly quoted table name; update
the code that sets query={`SELECT * FROM "${selectedTable}"`} to use the escaped
identifier logic for selectedTable before passing it into QueryDataTable.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e96f0e7 and a336b40.

⛔ Files ignored due to path filters (2)
  • docs/media/examples/multi-room.mp4 is excluded by !**/*.mp4
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • examples/multi-room/package.json
  • examples/multi-room/src/RoomPage.tsx
  • examples/multi-room/src/RoomsListPage.tsx
  • examples/multi-room/src/main.tsx
  • examples/multi-room/src/room-store.ts
  • examples/multi-room/src/rooms-list.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/multi-room/src/RoomsListPage.tsx
  • examples/multi-room/package.json
  • examples/multi-room/src/room-store.ts
  • examples/multi-room/src/main.tsx

Comment thread examples/multi-room/src/RoomPage.tsx Outdated

function RoomLoader({roomId}: {roomId: string}) {
const roomConfig = getRoom(roomId);
const storeRef = useRef<StoreApi<RoomState>>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

React 19 @types/react useRef TypeScript signature overloads

💡 Result:

In React 19 (@types/[email protected]), the TypeScript overloads for useRef were changed to fix long-standing “mutable vs readonly ref” issues and to simplify inference:

What changed (vs React 18)

React 18 (common overload shape)

useRef had overloads that could return either MutableRefObject<T> or RefObject<T> depending on whether your initial value included null. This is what caused the classic surprise:

  • useRef<HTMLInputElement>(null) → picked the T | null overload → returned a readonly RefObject<HTMLInputElement> (so ref.current = ... errored in TS)

(That overload-selection behavior is the root of many “why is current readonly?” questions.)

React 19 (new overload shape)

  • useRef now requires an argument (no more useRef()), and there’s a codemod for it.
  • All refs are now mutable, and MutableRef* is deprecated in favor of a single RefObject type.
  • Convenience overloads exist for null and undefined initial values so you still get the right T | null / T | undefined in current. (react.dev)

Practical “signatures/overloads” to know in React 19

Conceptually, you should think of React 19 useRef as:

  • useRef<T>(initialValue: T) -> RefObject<T> (mutable current)
  • useRef<T>(null) -> RefObject<T | null>
  • useRef<T>(undefined) -> RefObject<T | undefined>

And you must pass an argument, even if it’s undefined. (react.dev)

Migration / usage patterns

  • DOM ref:
    • const elRef = useRef<HTMLDivElement | null>(null); (then check for null)
  • “set later” value:
    • const idRef = useRef<number | undefined>(undefined);
  • If you previously wrote useRef<T>(), change to useRef<T | undefined>(undefined) or similar (codemod does this). (github.com)

Citations:


🏁 Script executed:

# Check the actual current state of RoomPage.tsx line 17
fd -t f "RoomPage.tsx" --exec cat -n {} \; | head -50

Repository: sqlrooms/sqlrooms

Length of output: 1985


Fix invalid useRef initialization for React 19 (breaks build with TS2554).

In React 19, useRef requires an initial argument; calling useRef<T>() without one is invalid. Update line 17 to provide both the initial value and correct type:

-  const storeRef = useRef<StoreApi<RoomState>>();
+  const storeRef = useRef<StoreApi<RoomState> | null>(null);
🧰 Tools
🪛 GitHub Actions: Build examples

[error] 17-17: TS2554: TypeScript error: Expected 1 arguments, but got 0. (src/RoomPage.tsx:17,20)

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

In `@examples/multi-room/src/RoomPage.tsx` at line 17, The useRef call for
storeRef is invalid under React 19 because useRef requires an initial value;
update the declaration of storeRef (currently useRef<StoreApi<RoomState>>()) to
supply an initial value and include null in the type to match nullable ref
semantics (e.g., useRef<StoreApi<RoomState> | null>(null)), so the ref is
properly typed and initialized for RoomPage components that reference
StoreApi<RoomState>.

Comment on lines +28 to +30
export function addRoom(room: RoomListEntry): void {
saveRoomsList([...getRoomsList(), room]);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

addRoom does not guard against duplicate IDs.

If a room with the same id is added twice (e.g., after partial state corruption or a re-seed), getRoom silently returns the stale first entry while deleteRoom removes all matches. A simple early-exit or deduplication prevents this:

🛡️ Proposed fix
 export function addRoom(room: RoomListEntry): void {
+  if (getRoomsList().some((r) => r.id === room.id)) return;
   saveRoomsList([...getRoomsList(), room]);
 }
📝 Committable suggestion

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

Suggested change
export function addRoom(room: RoomListEntry): void {
saveRoomsList([...getRoomsList(), room]);
}
export function addRoom(room: RoomListEntry): void {
if (getRoomsList().some((r) => r.id === room.id)) return;
saveRoomsList([...getRoomsList(), room]);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/rooms-list.ts` around lines 28 - 30, addRoom
currently appends a new RoomListEntry without checking for duplicate id, causing
getRoom to return a stale first match and deleteRoom to remove all matches;
update addRoom to guard against duplicates by using getRoomsList() to check for
an existing entry with the same id (RoomListEntry.id) and either early-return if
found or replace/deduplicate prior to calling saveRoomsList([...]) so the saved
list contains at most one entry per id; reference addRoom, getRoomsList,
saveRoomsList, getRoom, deleteRoom, and RoomListEntry.id when making the change.

Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (6)
examples/multi-room/src/RoomPage.tsx (2)

17-17: useRef missing-argument error addressed

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

In `@examples/multi-room/src/RoomPage.tsx` at line 17, The useRef declaration for
storeRef currently uses useRef<StoreApi<RoomState>>(null) which causes a
missing-argument/type error; update the generic to allow null (e.g.
useRef<StoreApi<RoomState> | null>(null)) or initialize with undefined
(useRef<StoreApi<RoomState> | undefined>(undefined)) so the type matches the
initial value; adjust any downstream null/undefined checks where storeRef is
accessed (storeRef.current) accordingly.

124-129: Table identifier not escaped in SQL query

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

In `@examples/multi-room/src/RoomPage.tsx` around lines 124 - 129, The SQL query
built for QueryDataTable uses raw selectedTable which can lead to SQL injection;
sanitize or validate the table identifier before constructing query. Ensure
selectedTable (used in the QueryDataTable prop query) is either validated
against a strict whitelist/regex (e.g. only letters, digits, underscores) or
escaped by doubling any quote characters and then wrapped in the proper
identifier quotes, and reject or refuse to run if it doesn't pass validation;
update the RoomPage.tsx location that builds `query={`SELECT * FROM
"${selectedTable}"`}` to perform this validation/escaping prior to passing the
query prop.
examples/multi-room/README.md (3)

5-5: alt is not a valid attribute on <video>

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

In `@examples/multi-room/README.md` at line 5, The README contains a <video>
element using an invalid alt attribute; remove the alt attribute from the
<video> tag in the README (examples/multi-room README's <video> element shown in
diff) and, if descriptive text is required for accessibility, add an aria-label
or aria-describedby on the <video> element or provide a nearby
caption/figcaption or an <img> fallback with an alt attribute describing the
video.

3-3: Github → GitHub

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

In `@examples/multi-room/README.md` at line 3, Change the link text from "Github
repo" to "GitHub repo" in the README entry that currently reads "[Github
repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)";
update the capitalization only (GitHub) so the link becomes "[GitHub
repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)".

24-26: pnpm install / pnpm dev need repo-root context and a workspace filter

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

In `@examples/multi-room/README.md` around lines 24 - 26, Update the README entry
for the multi-room example to note that the plain "pnpm install" and "pnpm dev"
commands must be run from the repository root or invoked with a pnpm workspace
filter; change the instructions to explicitly tell users to run dependency
installation at the repo root and/or run the dev script targeted to the example
workspace using pnpm's --filter workspace selector so the correct workspace
context is used.
examples/multi-room/src/RoomsListPage.tsx (1)

38-44: Duplicate renameRoom write from blur + submit still unguarded

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

In `@examples/multi-room/src/RoomsListPage.tsx` around lines 38 - 44,
confirmRename can be called twice (from blur and submit) causing duplicate
renameRoom writes; introduce a guard (e.g., a useRef renameInProgress) checked
at the top of confirmRename and set to true before calling renameRoom, then
reset after the async operation (or after loadRooms) so subsequent blur/submit
calls are no-ops; reference confirmRename, renameRoom, loadRooms, setEditingId,
editingId, and editName when applying the guard.
🧹 Nitpick comments (1)
examples/multi-room/src/RoomsListPage.tsx (1)

90-98: Native <button> for room navigation is inconsistent with <Button> used elsewhere

All other interactive elements in the list use the <Button> component from @sqlrooms/ui, but the room-name navigation control uses a raw <button>. This bypasses any shared styling, focus-ring, or variant tokens.

♻️ Suggested fix
-                  <button
-                    className="flex-1 text-left font-medium hover:underline"
-                    onClick={() =>
-                      navigate({to: '/room/$id', params: {id: room.id}})
-                    }
-                  >
-                    {room.name}
-                  </button>
+                  <Button
+                    variant="ghost"
+                    size="sm"
+                    className="flex-1 justify-start font-medium hover:underline"
+                    onClick={() =>
+                      navigate({to: '/room/$id', params: {id: room.id}})
+                    }
+                  >
+                    {room.name}
+                  </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/multi-room/src/RoomsListPage.tsx` around lines 90 - 98, Replace the
raw HTML <button> in RoomsListPage (the onClick that calls navigate({to:
'/room/$id', params: {id: room.id}}) and renders room.name) with the shared
Button component from `@sqlrooms/ui` so it inherits styling, focus rings, and
variants used elsewhere; keep the same onClick handler and className/props
(e.g., "flex-1 text-left font-medium hover:underline") or map them to the
Button's variant/size props, and ensure the Button renders room.name and calls
navigate with room.id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@examples/multi-room/README.md`:
- Line 5: The README contains a <video> element using an invalid alt attribute;
remove the alt attribute from the <video> tag in the README (examples/multi-room
README's <video> element shown in diff) and, if descriptive text is required for
accessibility, add an aria-label or aria-describedby on the <video> element or
provide a nearby caption/figcaption or an <img> fallback with an alt attribute
describing the video.
- Line 3: Change the link text from "Github repo" to "GitHub repo" in the README
entry that currently reads "[Github
repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)";
update the capitalization only (GitHub) so the link becomes "[GitHub
repo](https://github.com/sqlrooms/sqlrooms/tree/main/examples/multi-room)".
- Around line 24-26: Update the README entry for the multi-room example to note
that the plain "pnpm install" and "pnpm dev" commands must be run from the
repository root or invoked with a pnpm workspace filter; change the instructions
to explicitly tell users to run dependency installation at the repo root and/or
run the dev script targeted to the example workspace using pnpm's --filter
workspace selector so the correct workspace context is used.

In `@examples/multi-room/src/RoomPage.tsx`:
- Line 17: The useRef declaration for storeRef currently uses
useRef<StoreApi<RoomState>>(null) which causes a missing-argument/type error;
update the generic to allow null (e.g. useRef<StoreApi<RoomState> | null>(null))
or initialize with undefined (useRef<StoreApi<RoomState> |
undefined>(undefined)) so the type matches the initial value; adjust any
downstream null/undefined checks where storeRef is accessed (storeRef.current)
accordingly.
- Around line 124-129: The SQL query built for QueryDataTable uses raw
selectedTable which can lead to SQL injection; sanitize or validate the table
identifier before constructing query. Ensure selectedTable (used in the
QueryDataTable prop query) is either validated against a strict whitelist/regex
(e.g. only letters, digits, underscores) or escaped by doubling any quote
characters and then wrapped in the proper identifier quotes, and reject or
refuse to run if it doesn't pass validation; update the RoomPage.tsx location
that builds `query={`SELECT * FROM "${selectedTable}"`}` to perform this
validation/escaping prior to passing the query prop.

In `@examples/multi-room/src/RoomsListPage.tsx`:
- Around line 38-44: confirmRename can be called twice (from blur and submit)
causing duplicate renameRoom writes; introduce a guard (e.g., a useRef
renameInProgress) checked at the top of confirmRename and set to true before
calling renameRoom, then reset after the async operation (or after loadRooms) so
subsequent blur/submit calls are no-ops; reference confirmRename, renameRoom,
loadRooms, setEditingId, editingId, and editName when applying the guard.

---

Nitpick comments:
In `@examples/multi-room/src/RoomsListPage.tsx`:
- Around line 90-98: Replace the raw HTML <button> in RoomsListPage (the onClick
that calls navigate({to: '/room/$id', params: {id: room.id}}) and renders
room.name) with the shared Button component from `@sqlrooms/ui` so it inherits
styling, focus rings, and variants used elsewhere; keep the same onClick handler
and className/props (e.g., "flex-1 text-left font-medium hover:underline") or
map them to the Button's variant/size props, and ensure the Button renders
room.name and calls navigate with room.id.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a336b40 and f889f5b.

📒 Files selected for processing (4)
  • examples/multi-room/README.md
  • examples/multi-room/src/RoomPage.tsx
  • examples/multi-room/src/RoomsListPage.tsx
  • examples/multi-room/src/vite-env.d.ts
✅ Files skipped from review due to trivial changes (1)
  • examples/multi-room/src/vite-env.d.ts

@ilyabo
ilyabo merged commit 45ef14f into main Feb 25, 2026
5 checks passed
@ilyabo
ilyabo deleted the cursor/development-environment-setup-f050 branch February 25, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants