Skip to content

feat(database): injectable console page (schema browser + SQL panel) - #598

Merged
rohitg00 merged 2 commits into
feat/codeeditor-completionsfrom
feat/database-console-page
Jul 27, 2026
Merged

feat(database): injectable console page (schema browser + SQL panel)#598
rohitg00 merged 2 commits into
feat/codeeditor-completionsfrom
feat/database-console-page

Conversation

@rohitg00

@rohitg00 rohitg00 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Ships the database worker's #/ext/database page over the injectable-UI protocol (#570), the page that #580's database/ui deliberately left as a stub ("No page"):

  • schema tree with lazy per-driver column / PK / FK / index introspection
  • sortable, paginated row grid with a row inspector
  • read-only SQL panel: shared Monaco CodeEditor, EXPLAIN, per-db history, client-side write-verb gate

Reads the live database via database::query / database::listDatabases. Registered through host.pages.register in the existing database/ui setup, next to the function-trigger renderer. No console-source changes.

Stacked on feat/codeeditor-completions: the SQL editor passes table names + keywords to that PR's CodeEditor completions prop for as-you-type suggestions. Merge the console PR first.

Test

tsc + biome + esbuild clean. Live-verified end to end: the page injects and renders against the rig's sqlite (3 tables), the schema tree expands columns/PK/FK, the row grid sorts and inspects, and the SQL panel runs read-only queries with EXPLAIN, history, and table-name autocomplete.

Summary by CodeRabbit

  • New Features
    • Added a database browsing page for viewing configured databases, tables, and views.
    • Added schema exploration with columns, keys, relationships, and indexes.
    • Added paginated table data viewing with sorting, refresh, and row details.
    • Added read-only SQL execution, query history, autocomplete, and query plans.
    • Added copy-to-clipboard support and responsive light/dark themed styling.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 25, 2026 1:53pm
workers-tech-spec Ready Ready Preview, Comment Jul 25, 2026 1:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e24e2a45-6afc-4379-8a5a-c9538732919e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/database-console-page

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.

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 48 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@rohitg00
rohitg00 marked this pull request as ready for review July 24, 2026 18:00
Ships the database worker's #/ext/database page over the injectable-UI
protocol (#570): a schema tree with lazy per-driver column/PK/FK/index
introspection, a sortable paginated row grid with a row inspector, and a
read-only SQL panel (shared Monaco CodeEditor, EXPLAIN, per-db history,
client-side write-verb gate). Reads the live database via database::query
and database::listDatabases. Registered through host.pages.register in the
existing database/ui setup, alongside the function-trigger renderer.

The SQL editor feeds the live table names plus SQL keywords to the
CodeEditor completions prop for as-you-type suggestions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
database/ui/src/page/db-data.ts (1)

536-545: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clamp pageSize/offset to non-negative integers before interpolating.

Both go straight into the SQL text. They're typed number, so this isn't injectable, but a fractional or NaN value from any caller produces a syntax error rather than a clean failure.

♻️ Proposed guard
   const ref = quoteTableRef(driver, table)
-  const offset = page * pageSize
+  const limit = Math.max(1, Math.trunc(pageSize) || PAGE_SIZE)
+  const offset = Math.max(0, Math.trunc(page) || 0) * limit
   const orderBy = sort
     ? ` ORDER BY ${quoteIdent(driver, sort.column)} ${sort.dir === 'desc' ? 'DESC' : 'ASC'}`
     : ''
   const result = await runQuery(
     host,
     db,
-    `SELECT * FROM ${ref}${orderBy} LIMIT ${pageSize} OFFSET ${offset}`,
+    `SELECT * FROM ${ref}${orderBy} LIMIT ${limit} OFFSET ${offset}`,
   )
-  return { result, page, pageSize }
+  return { result, page, pageSize: limit }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/ui/src/page/db-data.ts` around lines 536 - 545, Before constructing
the SQL in the page query flow, validate and normalize pageSize and the derived
offset to non-negative integers, rejecting or safely handling fractional and
non-finite values. Update the interpolation in the runQuery call while
preserving the existing pagination behavior for valid inputs.
database/ui/src/page/result-grid.tsx (1)

150-201: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding aria-sort to sortable headers.

Sort state is only conveyed visually via the arrow icon; screen reader users get no indication of the current sort column/direction. Setting aria-sort="ascending"|"descending"|"none" on the <th> would close this gap cheaply.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/ui/src/page/result-grid.tsx` around lines 150 - 201, Update the
sortable header cells in the cols.map rendering to set aria-sort on each <th>
from the existing sorted value: use "ascending" or "descending" for the active
column and "none" otherwise. Keep the existing visual sort indicators and
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@database/ui/src/page/cells.ts`:
- Around line 17-23: The copyText function's try/catch block does not actually
handle promise rejections from navigator.clipboard.writeText, which returns a
Promise. The try/catch only guards the synchronous call site, leaving rejected
promises unhandled. Add a .catch() handler to the promise returned by
navigator.clipboard.writeText to suppress rejections, ensuring that
permission-denied and other async errors are actually swallowed as the comment
intends.

In `@database/ui/src/page/db-data.ts`:
- Around line 119-130: Update listDbs to throw when
listDatabasesResponseSchema.safeParse fails instead of returning an empty array,
matching runQuery’s shape-mismatch behavior. Preserve the existing database
mapping for successful responses so index.tsx can distinguish malformed
responses from an empty database list.
- Around line 289-311: Update isReadOnlySql and the transactionQuery SQL guard
to accept only one statement, detecting real statement separators while ignoring
semicolons inside strings or comments. Reject any trailing SQL after the first
statement, and explicitly reject writable PRAGMA forms such as journal_mode=WAL
while preserving genuinely read-only PRAGMAs.

In `@database/ui/src/page/icons.tsx`:
- Around line 13-46: Update the Svg component’s accessibility prop handling so
caller-supplied aria-hidden and aria-label values are preserved instead of being
overridden by the trailing aria-hidden="true". Keep the default behavior hidden
only when no accessibility override is provided, while allowing labeled icons
such as KeyRound to remain accessible.

In `@database/ui/src/page/SqlPanel.tsx`:
- Around line 109-115: Update the database-scoped state in the SqlPanel
component when db changes: reload history via loadHistory(db), clear existing
results and errors, and reset related UI state as appropriate. Also invalidate
or cancel in-flight requests during the db-change transition so responses from
the previous database cannot repopulate the panel or carry over its labels.
- Around line 117-181: Update the SQL execution path used by run and
runReadOnlySql to enforce read-only access at the database boundary rather than
relying on the leading-keyword check in SqlPanel. Reject mutating CTEs and
EXPLAIN ANALYZE statements, and configure the RPC worker’s transaction or
connection mode as read-only so writes cannot execute even when client-side
validation is bypassed.

In `@database/ui/src/page/TableDataPanel.tsx`:
- Around line 77-81: Update the TableDataPanel pagination flow to handle unknown
counts without unbounded navigation: incorporate countRead.error, determine
hasNext from an extra fetched row by requesting pageSize + 1 rows and trimming
the sentinel, and use that bounded result to disable or stop advancing past the
final available page. Preserve total-based pagination when the count is known
and update the related page-fetch/render logic consistently.

---

Nitpick comments:
In `@database/ui/src/page/db-data.ts`:
- Around line 536-545: Before constructing the SQL in the page query flow,
validate and normalize pageSize and the derived offset to non-negative integers,
rejecting or safely handling fractional and non-finite values. Update the
interpolation in the runQuery call while preserving the existing pagination
behavior for valid inputs.

In `@database/ui/src/page/result-grid.tsx`:
- Around line 150-201: Update the sortable header cells in the cols.map
rendering to set aria-sort on each <th> from the existing sorted value: use
"ascending" or "descending" for the active column and "none" otherwise. Keep the
existing visual sort indicators and behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d51d412-c7fe-435a-a8fd-d94df485695e

📥 Commits

Reviewing files that changed from the base of the PR and between 9a4a1ed and c03232f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • database/ui/package.json
  • database/ui/page.tsx
  • database/ui/src/page/RowDetail.tsx
  • database/ui/src/page/SchemaTree.tsx
  • database/ui/src/page/SqlPanel.tsx
  • database/ui/src/page/TableDataPanel.tsx
  • database/ui/src/page/cells.ts
  • database/ui/src/page/db-data.ts
  • database/ui/src/page/icons.tsx
  • database/ui/src/page/index.tsx
  • database/ui/src/page/page-styles.ts
  • database/ui/src/page/pagination.tsx
  • database/ui/src/page/result-grid.tsx
  • database/ui/src/page/useDatabaseRead.ts

Comment thread database/ui/src/page/cells.ts
Comment thread database/ui/src/page/db-data.ts
Comment thread database/ui/src/page/db-data.ts Outdated
Comment thread database/ui/src/page/icons.tsx
Comment thread database/ui/src/page/SqlPanel.tsx
Comment thread database/ui/src/page/SqlPanel.tsx
Comment thread database/ui/src/page/TableDataPanel.tsx Outdated
- read-only SQL gate hardened: single-statement only (semicolons in
  strings/comments ignored), reject any write/DDL keyword anywhere so
  mutating CTEs and EXPLAIN ANALYZE cannot slip past the leading keyword,
  and reject writable PRAGMA forms (journal_mode=WAL) while keeping read
  PRAGMAs
- listDbs throws on a shape mismatch like runQuery, so a malformed
  response is distinguishable from an empty database list
- fetchTablePage fetches a pageSize+1 sentinel row so pagination stays
  bounded when the row count is unknown; normalize page/size to
  non-negative integers before interpolating
- copyText swallows the async clipboard rejection via ?.catch, not just
  the sync call
- icons preserve caller aria-hidden/aria-label (labeled icons stay
  accessible) instead of forcing aria-hidden
- result grid sets aria-sort on sortable headers

Worker-side read-only transaction enforcement is out of scope here (it is
a database worker change); this tightens the client gate as defense in
depth. SqlPanel already remounts per database via its key, so no separate
db-change reset is needed.
@rohitg00
rohitg00 merged commit e0416aa into feat/codeeditor-completions Jul 27, 2026
16 checks passed
@rohitg00
rohitg00 deleted the feat/database-console-page branch July 27, 2026 08:56
rohitg00 added a commit that referenced this pull request Jul 27, 2026
#597)

* feat(console): CodeEditor completions prop for as-you-type suggestions

Adds an opt-in `completions?: readonly string[]` to the shared CodeEditor
(#579). Non-empty turns on the suggest popup (the default stays prose-quiet,
so markdown/json/yaml behavior is byte-for-byte unchanged) and registers a
Monaco completion provider for the editor's language offering those words,
disposed on unmount. The database SQL panel is the first consumer, feeding
it the live table names plus SQL keywords.

* feat(database): injectable console page (schema browser + SQL panel) (#598)

* feat(database): injectable console page (schema browser + SQL panel)

Ships the database worker's #/ext/database page over the injectable-UI
protocol (#570): a schema tree with lazy per-driver column/PK/FK/index
introspection, a sortable paginated row grid with a row inspector, and a
read-only SQL panel (shared Monaco CodeEditor, EXPLAIN, per-db history,
client-side write-verb gate). Reads the live database via database::query
and database::listDatabases. Registered through host.pages.register in the
existing database/ui setup, alongside the function-trigger renderer.

The SQL editor feeds the live table names plus SQL keywords to the
CodeEditor completions prop for as-you-type suggestions.

* fix(database): address review on the injectable page

- read-only SQL gate hardened: single-statement only (semicolons in
  strings/comments ignored), reject any write/DDL keyword anywhere so
  mutating CTEs and EXPLAIN ANALYZE cannot slip past the leading keyword,
  and reject writable PRAGMA forms (journal_mode=WAL) while keeping read
  PRAGMAs
- listDbs throws on a shape mismatch like runQuery, so a malformed
  response is distinguishable from an empty database list
- fetchTablePage fetches a pageSize+1 sentinel row so pagination stays
  bounded when the row count is unknown; normalize page/size to
  non-negative integers before interpolating
- copyText swallows the async clipboard rejection via ?.catch, not just
  the sync call
- icons preserve caller aria-hidden/aria-label (labeled icons stay
  accessible) instead of forcing aria-hidden
- result grid sets aria-sort on sortable headers

Worker-side read-only transaction enforcement is out of scope here (it is
a database worker change); this tightens the client gate as defense in
depth. SqlPanel already remounts per database via its key, so no separate
db-change reset is needed.

* fix(database): page edge cases from review

- Subtitle: a database configured without a `url` no longer reads "no
  database configured"; it falls back to the database name, and the
  worker-unavailable / nothing-configured copy stays for the absent cases.
- SQL panel is keyed by database only, so a refresh reloads metadata
  instead of remounting the editor and discarding an in-progress
  statement and its results.
- "open in sql" seeds through quoteTableRef, so a schema-qualified
  postgres table quotes component-wise instead of becoming one
  identifier.
- Selectable grid rows are tabbable and activate on Enter/Space, matching
  the click path; non-clickable rows are unchanged.
- SQL history state is bounded by HISTORY_LIMIT, matching what is
  persisted.
- Re-expanding a table whose schema read failed retries instead of
  staying on the error row forever.
- Copy feedback moves into a shared useCopyFeedback hook that cancels its
  pending timer on unmount, replacing the duplicate implementations in
  the grid and the row inspector.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant