Skip to content

feat(database): in-app Postgres/MySQL client (connect, schema browser, SQL query)#6983

Open
kenzouno1 wants to merge 21 commits into
stablyai:mainfrom
kenzouno1:feat/database-client
Open

feat(database): in-app Postgres/MySQL client (connect, schema browser, SQL query)#6983
kenzouno1 wants to merge 21 commits into
stablyai:mainfrom
kenzouno1:feat/database-client

Conversation

@kenzouno1

@kenzouno1 kenzouno1 commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Adds an in-app Database client for external Postgres/MySQL: connect, browse schema, run SQL, view results — without leaving Orca. Mirrors the SSH feature's connection-list + encrypted-secrets model. Drivers (pg, mysql2) are bundled and lazy-imported on first connect; all queries run in the main process over IPC gated to the trusted UI renderer. The sandboxed renderer never sees passwords — only password-stripped summaries.

Built in 5 phases (plan under plans/260630-1030-database-client-postgres-mysql/).

What's included

  • Connections — global add/edit/delete list + a Database top-level view. Passwords encrypted at rest via a strong safeStorage backend with fail-closed decrypt; on a weak/absent OS keystore, a banner + informed-consent warn-and-store (never a silent weak write).
  • LifecycleDbDriver abstraction, connect timeout (Promise.race), a mandatory pool 'error' listener (a dropped connection degrades to lost, never crashes the main process), connect-race guard, and quit-time disposal. SSL is smart-by-host (localhost→disable, remote→verify-full); LOCAL INFILE and multi-statement are disabled. IPC errors are normalized to a credential-free {code, safeMessage}.
  • Schema browser — lazy, capped introspection (schemas → tables → columns) on a pooled connection separate from the query path; virtualized, keyboard-navigable tree with overflow affordance.
  • Query workflow — Monaco SQL editor (Cmd/Ctrl+Enter), virtualized results grid (NULL vs empty, copy-cell, truncated indicator), and safe execution:
    • Read-only enforced at the database (PG SET TRANSACTION READ ONLY / MySQL START TRANSACTION READ ONLY), never by keyword matching. allowWrite is derived from the stored connection server-side and cannot be spoofed by the renderer.
    • Results bounded by a server-side cursor (PG DECLARE/FETCH rowLimit+1) or row-by-row stream (MySQL), never buffering the whole result; user SQL is never rewritten (no appended LIMIT).
    • Server-side cancel via a short-lived side connection (pg_cancel_backend / KILL QUERY) using the captured backend id.
    • Statement timeouts bound runtime; a conservative confirm dialog guards destructive statements on writable connections (the default).

Security posture / red-team

Planning included a 15-finding red-team pass; all Critical/High items are addressed (drivers in the packaged-runtime allowlist, fail-closed credential store, DB-enforced read-only, unhandled-'error' crash fix, connect timeout, error redaction, capped introspection, real server-side cancel, trusted-sender IPC gate, Monaco SQL registration).

A post-implementation branch review (report in plans/reports/) found 2 High + 2 Medium + Lows, all fixed in the final commit:

  • H1 — Postgres read-only bypass via multi-statement SET TRANSACTION READ WRITE; … → read-only connections now reject multi-statement input.
  • H2 — MySQL COMMIT on a stream-poisoned connection after a truncated read → COMMIT skipped; connection destroyed instead (no lost rows / no full-buffer).
  • M1 — raw NUL byte in a store slice made it binary to git → escaped.
  • M2 — unbounded introspection runtime → statement timeout applied (PG pool statement_timeout, MySQL per-query timeout).
  • L1/L3 — MySQL writes bounded by a client timeout; one query per connection at a time (cancel targets the right query).

Testing

  • 213 unit tests across credential store, persistence, IPC (trust gating, password stripping, allowWrite server-derivation), drivers (SSL/config, connect resilience, introspection mapping/caps, query read-only txn + cursor/stream bounding + cancel), the SQL classifier, and the schema-tree flattener.
  • typecheck (node + web) ✓ · oxlint + switch-exhaustiveness ✓ · localization catalog parity + coverage ✓ (keys added across en/es/ja/ko/zh).

Known limitations / follow-ups

  • Queries dial from the local main process; when the active workspace is SSH-remote this is a silent limitation (the sshTunnel model field is reserved but the tunnel is not wired) — a UI disclosure + true tunnel are follow-ups.
  • Result column dataType is names-only for now (no OID/type-code → name map); headers render correctly.
  • Driver behavior is covered by unit tests with mocks; live PG/MySQL integration tests (docker) are a recommended follow-up to exercise real read-only rejection, cursor streaming, and cancel end-to-end.

Notes for reviewers

Cross-platform (macOS/Linux/Windows) via CmdOrCtrl / platform checks; STYLEGUIDE tokens + shadcn primitives for UI; no max-lines disables.


Open in Stage

kenzouno1 added 3 commits July 1, 2026 08:51
…wser

In-app database client for external Postgres/MySQL servers. Drivers (pg,
mysql2) are bundled and lazy-imported on first connect; all queries run in
the main process over IPC gated to the trusted UI renderer. The renderer
never sees passwords, only password-stripped summaries.

Connections
- Global connection list with add/edit/delete and a Database top-level view
- Passwords encrypted via a strong safeStorage backend; fail-closed decrypt,
  warn-and-store behind a banner when the OS keystore is weak or absent

Lifecycle
- Driver abstraction with connect timeout and a mandatory 'error' listener,
  so a dropped connection degrades to lost instead of crashing the main
  process; connect-race guard and quit-time disposal
- Pooled connections; SSL smart-by-host (localhost disable, remote
  verify-full); LOCAL INFILE and multi-statement disabled
- IPC errors normalized to a safe {code, message} allow-list, never raw DSNs

Schema browser
- Lazy, capped introspection (schemas then tables then columns) on a pooled
  connection separate from the query path; overflow surfaced when capped
- Virtualized, keyboard-navigable tree with per-node lazy load and refresh
Add the query workflow to the database client: a Monaco SQL editor, a
virtualized results grid, and query execution that enforces safety at the
database rather than by string matching.

- Read-only connections run reads inside a database read-only transaction
  (Postgres BEGIN + SET TRANSACTION READ ONLY, MySQL START TRANSACTION READ
  ONLY); allowWrite is derived from the stored connection server-side and never
  trusted from the renderer
- Results are bounded by a server-side cursor (Postgres DECLARE/FETCH) or a
  row-by-row stream (MySQL) capped at rowLimit+1; the user SQL is never rewritten
- Running queries are cancellable server-side via a short-lived side connection
  (pg_cancel_backend / KILL QUERY) using the captured backend id
- Statement timeouts bound runtime; a conservative confirm dialog guards
  destructive statements on writable connections
- Monaco SQL basic-language registered for highlighting

A shared SQL classifier drives the cursor path and the confirm dialog only; it
is never the security boundary.
… introspection

Address code-review findings on the database client:

- Reject multi-statement input on read-only connections. A Postgres simple query
  runs every statement, so a multi-statement string could issue SET TRANSACTION
  READ WRITE before any query and defeat the read-only transaction; the manager
  now blocks multi-statement when writes are disallowed (the read-only txn still
  covers single-statement writes and writing CTEs).
- Skip COMMIT on a MySQL connection poisoned by an early stream teardown. On a
  truncated read the connection has undrained packets; committing would either
  desync and lose the valid rows or drain the whole result. The read-only
  transaction is abandoned when the connection is destroyed instead.
- Store the column-cache key separator as an escaped code unit instead of a raw
  NUL byte so the store slice stays a text file (was diffed as binary).
- Bound introspection runtime: Postgres statement_timeout on the pool; MySQL a
  per-query client timeout. Also bound MySQL writes with a client timeout
  (max_execution_time only covers SELECT).
- Reject a second concurrent query on the same connection so cancel always
  targets the running query.
Radix <SelectItem> forbids an empty-string value (it is reserved for clearing
the selection). The SSL field used value="" for the "Auto (smart by host)"
option, which threw and crashed the connection form's error boundary when the
dropdown opened. Use an 'auto' sentinel that maps to undefined in the payload,
and add a regression test asserting the SSL field never yields an empty string.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

Changes

This PR adds database support across shared types, storage, Postgres/MySQL drivers, IPC, preload APIs, renderer state, and database UI. It introduces connection CRUD, secret storage, schema browsing, query and batch execution, table-data editing, a database page, and locale strings for the new feature.

Sequence Diagram(s)

sequenceDiagram
  participant Renderer
  participant PreloadAPI
  participant DatabaseIPC
  participant DbConnectionManager
  participant Driver

  Renderer->>PreloadAPI: database.query / execute / executeBatch
  PreloadAPI->>DatabaseIPC: ipcRenderer.invoke(...)
  DatabaseIPC->>DbConnectionManager: query / execute / executeBatch
  DbConnectionManager->>Driver: delegated DB operation
  Driver-->>DbConnectionManager: result or DbSafeError
  DbConnectionManager-->>DatabaseIPC: response payload
  DatabaseIPC-->>PreloadAPI: typed IPC result
  PreloadAPI-->>Renderer: database result
Loading

Suggested Labels

feature, database

Suggested Reviewers

Not enough information available to suggest specific reviewers.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers most functionality, but it omits the required Screenshots section and lacks an explicit AI Review Report section. Add a Screenshots section (or state 'No visual change') and include a dedicated AI Review Report with risks checked, findings, changes made, and cross-platform verification.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: an in-app Postgres/MySQL database client with connect, schema browsing, and SQL query support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

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

🧹 Nitpick comments (2)
src/renderer/src/components/database/QueryWorkspace.tsx (1)

79-81: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Shortcut label should be platform-specific, not "⌘/Ctrl" combined.

Showing both symbols together doesn't match the platform-specific convention and is confusing on non-Mac systems (no ⌘ key exists there).

♻️ Suggested platform-specific label
+const isMac = navigator.userAgent.includes('Mac')
+
 ...
         <span className="ml-auto text-[11px] text-muted-foreground">
-          {translate('auto.components.database.QueryWorkspace.runHint', '⌘/Ctrl + Enter to run')}
+          {translate(
+            'auto.components.database.QueryWorkspace.runHint',
+            isMac ? '⌘ + Enter to run' : 'Ctrl + Enter to run'
+          )}
         </span>

Note: the en.json (and other locale) fallback string for this key should be updated to match once this is fixed.

Based on learnings/path instructions: "Use `CmdOrCtrl` for Electron menu accelerators and display `⌘` / `⇧` on Mac and `Ctrl+` / `Shift+` on other platforms in UI shortcut labels."

Source: Path instructions

src/renderer/src/store/slices/database.ts (1)

182-191: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication between applyDbStatus and the subscribeDbStatusChanges callback.

Both blocks do the identical set(...dbStatuses...) + dropCacheForNonLive(state) sequence. Could be consolidated into a single internal helper (e.g. applyStatusUpdate) called from all three sites (including the disconnect fix above).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a9f1cd97-e201-4a8c-b8fc-1c3013dd703c

📥 Commits

Reviewing files that changed from the base of the PR and between 8a805e2 and 2ddbe72.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (66)
  • config/packaged-runtime-node-modules.cjs
  • package.json
  • src/main/database/db-connection-manager.test.ts
  • src/main/database/db-connection-manager.ts
  • src/main/database/db-credential-store.test.ts
  • src/main/database/db-credential-store.ts
  • src/main/database/db-driver.test.ts
  • src/main/database/db-driver.ts
  • src/main/database/mysql-driver.test.ts
  • src/main/database/mysql-driver.ts
  • src/main/database/mysql-introspection-queries.test.ts
  • src/main/database/mysql-introspection-queries.ts
  • src/main/database/mysql-query.test.ts
  • src/main/database/mysql-query.ts
  • src/main/database/postgres-driver.test.ts
  • src/main/database/postgres-driver.ts
  • src/main/database/postgres-introspection-queries.test.ts
  • src/main/database/postgres-introspection-queries.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts
  • src/main/index.ts
  • src/main/ipc/database.test.ts
  • src/main/ipc/database.ts
  • src/main/ipc/register-core-handlers.test.ts
  • src/main/ipc/register-core-handlers.ts
  • src/main/ipc/ui.ts
  • src/main/persistence-db-connections.test.ts
  • src/main/persistence.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/App.tsx
  • src/renderer/src/components/database/ConnectionForm.tsx
  • src/renderer/src/components/database/ConnectionList.tsx
  • src/renderer/src/components/database/DatabasePage.tsx
  • src/renderer/src/components/database/QueryEditor.tsx
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/renderer/src/components/database/connection-encryption-warning.tsx
  • src/renderer/src/components/database/connection-form-defaults.ts
  • src/renderer/src/components/database/connection-status-indicator.tsx
  • src/renderer/src/components/database/schema-tree-rows.test.ts
  • src/renderer/src/components/database/schema-tree-rows.ts
  • src/renderer/src/components/sidebar/SidebarNav.tsx
  • src/renderer/src/hooks/resolve-zoom-target.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/lib/monaco-sql-language.test.ts
  • src/renderer/src/lib/monaco-sql-language.ts
  • src/renderer/src/lib/right-sidebar-visibility.test.ts
  • src/renderer/src/lib/right-sidebar-visibility.ts
  • src/renderer/src/store/index.ts
  • src/renderer/src/store/slices/database.ts
  • src/renderer/src/store/slices/diffComments.test.ts
  • src/renderer/src/store/slices/store-test-helpers.ts
  • src/renderer/src/store/slices/ui.ts
  • src/renderer/src/store/types.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/constants.ts
  • src/shared/database-types.ts
  • src/shared/sql-statement-classifier.test.ts
  • src/shared/sql-statement-classifier.ts
  • src/shared/types.ts

Comment thread src/main/database/db-connection-manager.ts Outdated
Comment thread src/main/database/db-connection-manager.ts
Comment thread src/main/database/db-driver.ts
Comment thread src/main/database/mysql-driver.ts
Comment thread src/main/database/postgres-introspection-queries.ts
Comment thread src/main/persistence-db-connections.test.ts Outdated
Comment thread src/renderer/src/components/database/ResultsGrid.tsx Outdated
Comment thread src/renderer/src/lib/monaco-sql-language.ts
Comment thread src/renderer/src/store/slices/database.ts
Comment thread src/shared/sql-statement-classifier.ts Outdated
kenzouno1 added 5 commits July 1, 2026 10:34
…L parsing

- await pool teardown during app quit so connections close before exit
- validate partial connection updates before persisting them
- scope driver error listeners per connection; clear in-flight state on
  disconnect/reconnect so a replacement pool isn't torn down by a late error
- bound the MySQL validation ping and guard timeout teardown callbacks
- run writable transaction-block-unsafe statements in autocommit
- fix Postgres primary-key introspection join across table name
- detect statement separators inside quoted identifiers and strings
- retry SQL language registration after a failed chunk load
- toast copy success only after the clipboard write resolves
- show the platform-specific run-shortcut modifier
Clicking a table or view in the schema browser loads a bounded SELECT
into the query editor and runs it, so the generated SQL stays visible
and editable. The chevron owns expand/collapse so it no longer competes
with the row's primary action. Identifiers are engine-quoted (doubled
quote char) to keep reserved/quoted names from breaking out.

@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.

🧹 Nitpick comments (1)
plans/260630-1030-database-client-postgres-mysql/phase-05-query-editor-and-results-grid.md (1)

89-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor wording redundancy: "SQL language" → "SQL".

"language" is redundant since it's registering the Monaco "SQL" basic-language.

Source: Linters/SAST tools


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e4115d94-41d2-4e90-9e5e-cc4c9165299e

📥 Commits

Reviewing files that changed from the base of the PR and between cdd246f and f671e8d.

📒 Files selected for processing (40)
  • plans/260630-1030-database-client-postgres-mysql/phase-01-scaffold-and-driver-deps.md
  • plans/260630-1030-database-client-postgres-mysql/phase-02-connection-model-and-secure-persistence.md
  • plans/260630-1030-database-client-postgres-mysql/phase-03-driver-abstraction-and-connect.md
  • plans/260630-1030-database-client-postgres-mysql/phase-04-schema-browser.md
  • plans/260630-1030-database-client-postgres-mysql/phase-05-query-editor-and-results-grid.md
  • plans/260630-1030-database-client-postgres-mysql/plan.md
  • plans/reports/brainstorm-260630-1030-database-client-postgres-mysql-report.md
  • plans/reports/from-code-reviewer-to-controller-database-client-branch-review.md
  • plans/reports/fullstack-developer-260701-0750-database-client-phase3-driver-connect-report.md
  • plans/reports/fullstack-developer-260701-0823-database-client-phase4-schema-browser-report.md
  • plans/reports/fullstack-developer-260701-0848-database-client-phase5-query-editor-report.md
  • plans/reports/pr6983-stagereview-risk-review-and-fixes-report.md
  • plans/reports/tester-260630-1736-database-client-phase2-test-report.md
  • src/main/database/db-connection-manager.test.ts
  • src/main/database/db-connection-manager.ts
  • src/main/database/db-driver.test.ts
  • src/main/database/db-driver.ts
  • src/main/database/mysql-driver.ts
  • src/main/database/postgres-introspection-queries.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts
  • src/main/index.ts
  • src/main/ipc/database.test.ts
  • src/main/ipc/database.ts
  • src/main/persistence-db-connections.test.ts
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/lib/monaco-sql-language.test.ts
  • src/renderer/src/lib/monaco-sql-language.ts
  • src/renderer/src/store/slices/database.ts
  • src/shared/sql-statement-classifier.test.ts
  • src/shared/sql-statement-classifier.ts
  • src/shared/table-preview-query.test.ts
  • src/shared/table-preview-query.ts
✅ Files skipped from review due to trivial changes (8)
  • plans/reports/fullstack-developer-260701-0848-database-client-phase5-query-editor-report.md
  • src/shared/table-preview-query.test.ts
  • plans/260630-1030-database-client-postgres-mysql/phase-04-schema-browser.md
  • plans/reports/fullstack-developer-260701-0750-database-client-phase3-driver-connect-report.md
  • plans/260630-1030-database-client-postgres-mysql/phase-01-scaffold-and-driver-deps.md
  • plans/260630-1030-database-client-postgres-mysql/phase-02-connection-model-and-secure-persistence.md
  • plans/reports/fullstack-developer-260701-0823-database-client-phase4-schema-browser-report.md
  • src/renderer/src/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (23)
  • src/renderer/src/lib/monaco-sql-language.test.ts
  • src/renderer/src/lib/monaco-sql-language.ts
  • src/main/index.ts
  • src/shared/sql-statement-classifier.test.ts
  • src/main/database/mysql-driver.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/db-driver.ts
  • src/renderer/src/i18n/locales/es.json
  • src/shared/sql-statement-classifier.ts
  • src/main/database/postgres-query.ts
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/main/database/db-driver.test.ts
  • src/main/ipc/database.test.ts
  • src/main/persistence-db-connections.test.ts
  • src/renderer/src/store/slices/database.ts
  • src/main/database/db-connection-manager.test.ts
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/renderer/src/i18n/locales/zh.json
  • src/main/database/postgres-introspection-queries.ts
  • src/main/ipc/database.ts
  • src/main/database/db-connection-manager.ts
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/components/database/SchemaTree.tsx

kenzouno1 added 3 commits July 1, 2026 11:41
…action menus

Move connect/disconnect/edit/delete into an overflow menu and right-click context menu sharing one action list; double-click or Enter opens a live connection or connects an idle one. Highlight the whole row for the active connection and simplify the metadata line to a status dot, name, and engine badge.
Add a DBeaver-style data grid on top of the DB client: a tabbed workspace
(a permanent Query tab plus a Data tab per opened table), server-side
sort/filter/pagination, and staged cell edit + insert + delete applied
atomically and keyed by primary key. Free-form query results gain the same
sort/filter/paginate by wrapping the read as a subquery (ordinal sort so
duplicate column names still work).

Parameterized writes run through new trusted-gated database:execute and
database:executeBatch IPC channels; allowWrite stays server-derived from the
stored readOnly, and editing is enabled only for tables with a primary key on
a writable connection.
@kenzouno1

Copy link
Copy Markdown
Author

Update: editable data grid (sort / filter / edit)

Pushed a DBeaver-style data grid on top of the client (was out of scope in v1):

  • Tabbed workspace — a permanent Query tab plus a Data tab per table opened from the schema tree.
  • Data tab — server-side sort (click header), per-column filter (popover), and pagination; cell edit + add/delete row, staged then applied atomically via a new transactional database:executeBatch, keyed by primary key. Editing is enabled only for tables with a PK on a writable connection (no-PK / read-only → read-only with a badge).
  • Query tab — free-form SELECT results get the same sort/filter/paginate by wrapping the read as a subquery (ordinal sort survives duplicate column names; non-SELECT leaves headers inert).

Writes are parameterized (values as bind params, identifiers quote-escaped) through new trusted-gated database:execute / database:executeBatch; allowWrite stays server-derived from the stored readOnly.

Verification: 1757 unit tests; typecheck:node/web/cli, switch-exhaustiveness, styled-scrollbars, and localization catalog + coverage all green. Live PG/MySQL docker pass recommended as follow-up. See plans/reports/implementation-260701-1105-database-data-grid-editor-report.md.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Caution

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

Error details
{}

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

🧹 Nitpick comments (3)
src/renderer/src/components/database/data-grid-cell-format.ts (1)

7-15: 🎯 Functional Correctness | 🔵 Trivial

Consider special-casing Date/binary values in formatCell.

typeof value === 'object' routes Date and any binary blob (bytea/BLOB columns) through JSON.stringify, producing a quoted ISO string or a noisy array/object dump instead of a readable representation — relevant for a DB client that will render arbitrary column types.

♻️ Proposed refactor
 export function formatCell(value: unknown): { text: string; isNull: boolean } {
   if (value === null || value === undefined) {
     return { text: 'NULL', isNull: true }
   }
+  if (value instanceof Date) {
+    return { text: value.toISOString(), isNull: false }
+  }
   if (typeof value === 'object') {
     return { text: JSON.stringify(value), isNull: false }
   }
   return { text: String(value), isNull: false }
 }
src/renderer/src/components/database/TableDataView.tsx (1)

83-87: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard saving state against a thrown saveDbEdits.

If saveDbEdits ever rejects (vs. catching internally and writing to view.saveError, as queryState.error does elsewhere), saving stays true forever, permanently disabling Save/Revert for that tab. Worth a try/finally regardless of current store-action behavior.

🛡️ Suggested fix
   const onSave = async (): Promise<void> => {
     setSaving(true)
-    await saveDbEdits(connectionId, tabId)
-    setSaving(false)
+    try {
+      await saveDbEdits(connectionId, tabId)
+    } finally {
+      setSaving(false)
+    }
   }
src/main/ipc/database.ts (1)

296-368: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Extract the repeated allowWrite derivation into a shared helper.

The same 2-line server-side allowWrite derivation is now duplicated across database:query, database:execute, and database:executeBatch. Since this is the core read-only enforcement check (explicitly called out in the PR objectives as security-critical), consider extracting it once to reduce the chance that a future edit updates one call site but not the others.

♻️ Suggested extraction
+  const resolveAllowWrite = (id: string): boolean => {
+    const connection = store.getDbConnection(id)
+    return connection ? !connection.readOnly : false
+  }
+
   ipcMain.handle(
     'database:query',
     async (event, args: { id: string; sql: string }): Promise<DbQueryResult> => {
       requireTrusted(event.sender)
       try {
-        const connection = store.getDbConnection(args.id)
-        const allowWrite = connection ? !connection.readOnly : false
+        const allowWrite = resolveAllowWrite(args.id)
         const result = await dbConnectionManager.query(args.id, args.sql, {

(repeat similarly for database:execute and database:executeBatch)


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e0b96ed9-0232-4e62-9e43-9ae853baacab

📥 Commits

Reviewing files that changed from the base of the PR and between f671e8d and d8f9fe4.

📒 Files selected for processing (54)
  • config/localization-coverage-allowlist.json
  • plans/reports/implementation-260701-1105-database-data-grid-editor-report.md
  • src/main/database/db-connection-manager.test.ts
  • src/main/database/db-connection-manager.ts
  • src/main/database/db-driver.ts
  • src/main/database/mysql-driver.ts
  • src/main/database/mysql-query.test.ts
  • src/main/database/mysql-query.ts
  • src/main/database/postgres-driver.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts
  • src/main/ipc/database.test.ts
  • src/main/ipc/database.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/components/database/ConnectionList.tsx
  • src/renderer/src/components/database/DataGridColumnHeader.tsx
  • src/renderer/src/components/database/DatabasePage.tsx
  • src/renderer/src/components/database/DatabaseWorkspace.tsx
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/renderer/src/components/database/TableDataCell.tsx
  • src/renderer/src/components/database/TableDataCellEditor.tsx
  • src/renderer/src/components/database/TableDataGrid.tsx
  • src/renderer/src/components/database/TableDataRowMenu.tsx
  • src/renderer/src/components/database/TableDataView.tsx
  • src/renderer/src/components/database/connection-row.tsx
  • src/renderer/src/components/database/connection-status-indicator.tsx
  • src/renderer/src/components/database/data-grid-cell-format.ts
  • src/renderer/src/components/database/data-grid-column-filter.tsx
  • src/renderer/src/components/database/data-grid-filters.test.ts
  • src/renderer/src/components/database/data-grid-filters.ts
  • src/renderer/src/components/database/data-grid-sort-state.test.ts
  • src/renderer/src/components/database/data-grid-sort-state.ts
  • src/renderer/src/components/database/table-data-edit-buffer.test.ts
  • src/renderer/src/components/database/table-data-edit-buffer.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/database-workspace-tabs.test.ts
  • src/renderer/src/store/database-workspace-tabs.ts
  • src/renderer/src/store/slices/database-query-refine.test.ts
  • src/renderer/src/store/slices/database-table-data.test.ts
  • src/renderer/src/store/slices/database-table-edit.test.ts
  • src/renderer/src/store/slices/database.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/database-types.ts
  • src/shared/sql-identifier.ts
  • src/shared/table-data-sql.test.ts
  • src/shared/table-data-sql.ts
  • src/shared/table-preview-query.ts
✅ Files skipped from review due to trivial changes (4)
  • src/renderer/src/components/database/data-grid-filters.test.ts
  • src/shared/sql-identifier.ts
  • src/renderer/src/components/database/data-grid-sort-state.test.ts
  • src/renderer/src/components/database/data-grid-sort-state.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • src/shared/table-preview-query.ts
  • src/renderer/src/components/database/DatabasePage.tsx
  • src/preload/api-types.ts
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/renderer/src/web/web-preload-api.ts
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/main/database/mysql-driver.ts
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/ja.json
  • src/main/database/db-connection-manager.ts
  • src/main/database/db-driver.ts
  • src/renderer/src/i18n/locales/en.json

Comment thread src/main/database/mysql-query.ts Outdated
Comment thread src/main/database/mysql-query.ts Outdated
Comment thread src/main/database/postgres-query.test.ts
Comment thread src/main/database/postgres-query.ts Outdated
Comment thread src/main/database/postgres-query.ts
Comment thread src/renderer/src/components/database/table-data-edit-buffer.ts
Comment thread src/renderer/src/store/slices/database.ts
Comment thread src/shared/database-types.ts Outdated
Address review feedback on the data grid editor:
- executeBatch runs a read-only transaction when !allowWrite (pg + mysql) so a
  read-only connection stays DB-enforced on the write path too.
- bufferToStatements fails fast when a staged row cannot be keyed rather than
  binding undefined into an UPDATE/DELETE; saveDbEdits surfaces it as a save error.
- table-data loads and free-form query runs/refines carry a sequence guard so a
  slow earlier response cannot overwrite a newer sort/filter/page result.
- formatCell renders Date as ISO and binary columns as a byte count.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/renderer/src/components/database/data-grid-cell-format.test.ts (1)

1-32: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No test coverage for copyCell.

Only formatCell is tested; copyCell's clipboard-write + success/error toast branching (lines 25-33 in data-grid-cell-format.ts) is untested. Consider adding tests that mock navigator.clipboard.writeText to resolve/reject and assert the correct toast is shown, and that NULL values are copied as an empty string rather than the literal "NULL".


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 921b0d14-1ca5-4330-9ae7-fb2870a7b02a

📥 Commits

Reviewing files that changed from the base of the PR and between f671e8d and a6e516b.

📒 Files selected for processing (55)
  • config/localization-coverage-allowlist.json
  • plans/reports/implementation-260701-1105-database-data-grid-editor-report.md
  • src/main/database/db-connection-manager.test.ts
  • src/main/database/db-connection-manager.ts
  • src/main/database/db-driver.ts
  • src/main/database/mysql-driver.ts
  • src/main/database/mysql-query.test.ts
  • src/main/database/mysql-query.ts
  • src/main/database/postgres-driver.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts
  • src/main/ipc/database.test.ts
  • src/main/ipc/database.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/components/database/ConnectionList.tsx
  • src/renderer/src/components/database/DataGridColumnHeader.tsx
  • src/renderer/src/components/database/DatabasePage.tsx
  • src/renderer/src/components/database/DatabaseWorkspace.tsx
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/renderer/src/components/database/TableDataCell.tsx
  • src/renderer/src/components/database/TableDataCellEditor.tsx
  • src/renderer/src/components/database/TableDataGrid.tsx
  • src/renderer/src/components/database/TableDataRowMenu.tsx
  • src/renderer/src/components/database/TableDataView.tsx
  • src/renderer/src/components/database/connection-row.tsx
  • src/renderer/src/components/database/connection-status-indicator.tsx
  • src/renderer/src/components/database/data-grid-cell-format.test.ts
  • src/renderer/src/components/database/data-grid-cell-format.ts
  • src/renderer/src/components/database/data-grid-column-filter.tsx
  • src/renderer/src/components/database/data-grid-filters.test.ts
  • src/renderer/src/components/database/data-grid-filters.ts
  • src/renderer/src/components/database/data-grid-sort-state.test.ts
  • src/renderer/src/components/database/data-grid-sort-state.ts
  • src/renderer/src/components/database/table-data-edit-buffer.test.ts
  • src/renderer/src/components/database/table-data-edit-buffer.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/database-workspace-tabs.test.ts
  • src/renderer/src/store/database-workspace-tabs.ts
  • src/renderer/src/store/slices/database-query-refine.test.ts
  • src/renderer/src/store/slices/database-table-data.test.ts
  • src/renderer/src/store/slices/database-table-edit.test.ts
  • src/renderer/src/store/slices/database.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/database-types.ts
  • src/shared/sql-identifier.ts
  • src/shared/table-data-sql.test.ts
  • src/shared/table-data-sql.ts
  • src/shared/table-preview-query.ts
🚧 Files skipped from review as they are similar to previous changes (50)
  • src/renderer/src/components/database/data-grid-sort-state.test.ts
  • config/localization-coverage-allowlist.json
  • src/renderer/src/components/database/data-grid-filters.test.ts
  • src/shared/sql-identifier.ts
  • src/renderer/src/components/database/TableDataRowMenu.tsx
  • src/shared/table-preview-query.ts
  • src/renderer/src/store/database-workspace-tabs.test.ts
  • src/renderer/src/components/database/DatabasePage.tsx
  • src/renderer/src/web/web-preload-api.ts
  • src/renderer/src/components/database/DatabaseWorkspace.tsx
  • src/renderer/src/components/database/data-grid-column-filter.tsx
  • src/renderer/src/store/slices/database-table-edit.test.ts
  • src/renderer/src/components/database/connection-status-indicator.tsx
  • src/renderer/src/components/database/TableDataCellEditor.tsx
  • src/renderer/src/components/database/TableDataCell.tsx
  • src/renderer/src/components/database/table-data-edit-buffer.test.ts
  • src/renderer/src/components/database/DataGridColumnHeader.tsx
  • src/renderer/src/store/slices/database-table-data.test.ts
  • src/renderer/src/components/database/data-grid-sort-state.ts
  • src/renderer/src/components/database/connection-row.tsx
  • src/renderer/src/components/database/ConnectionList.tsx
  • src/shared/table-data-sql.test.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/store/database-workspace-tabs.ts
  • src/main/database/postgres-query.test.ts
  • src/renderer/src/components/database/TableDataGrid.tsx
  • src/renderer/src/store/slices/database-query-refine.test.ts
  • src/main/database/postgres-driver.ts
  • src/renderer/src/components/database/TableDataView.tsx
  • src/preload/index.ts
  • src/shared/table-data-sql.ts
  • src/renderer/src/i18n/locales/es.json
  • src/main/database/db-driver.ts
  • src/preload/api-types.ts
  • src/main/database/mysql-driver.ts
  • src/main/database/mysql-query.test.ts
  • src/main/ipc/database.ts
  • src/renderer/src/components/database/data-grid-filters.ts
  • src/renderer/src/components/database/table-data-edit-buffer.ts
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/renderer/src/components/database/QueryWorkspace.tsx
  • src/main/database/db-connection-manager.test.ts
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/renderer/src/store/slices/database.ts
  • src/main/database/db-connection-manager.ts
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/shared/database-types.ts
  • src/main/ipc/database.test.ts
  • src/renderer/src/i18n/locales/zh.json

Comment thread plans/reports/implementation-260701-1105-database-data-grid-editor-report.md Outdated
Comment thread src/renderer/src/components/database/data-grid-cell-format.ts
kenzouno1 added 3 commits July 1, 2026 15:51
MySQL verify-full only validated the certificate chain, not the server hostname (mysql2 gates that on verifyIdentity), so a CA-signed cert issued for another host could MITM a remote connection. Set verifyIdentity for verify-full so the hostname is checked too, matching Postgres.

Return BIGINT/DECIMAL as strings (supportBigNumbers + bigNumberStrings) so values above 2^53 keep full precision on read and on PK-keyed edits.
Block inline editing of binary/array/JSON cells and compare cells by their display form, so an unchanged complex cell can no longer stage its lossy text (e.g. "[3 bytes]") as a write. Disable editing during an in-flight reload and confirm before closing a Data tab with unsaved edits.

Make result cells natively text-selectable (drop click-to-copy). Also guard schema-tree double-load, use a monotonic load epoch, drop stale row-count responses, block save while encryption status is still loading, and fix schema-tree keyboard navigation.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 65294a0c-5b3b-4e4a-bc92-9fb5147b7527

📥 Commits

Reviewing files that changed from the base of the PR and between a6e516b and 37b42ef.

📒 Files selected for processing (12)
  • src/main/database/mysql-driver.test.ts
  • src/main/database/mysql-driver.ts
  • src/renderer/src/components/database/ConnectionForm.tsx
  • src/renderer/src/components/database/DatabaseWorkspace.tsx
  • src/renderer/src/components/database/ResultsGrid.tsx
  • src/renderer/src/components/database/SchemaTree.tsx
  • src/renderer/src/components/database/TableDataCell.tsx
  • src/renderer/src/components/database/TableDataView.tsx
  • src/renderer/src/components/database/data-grid-cell-format.ts
  • src/renderer/src/components/database/table-data-edit-buffer.test.ts
  • src/renderer/src/components/database/table-data-edit-buffer.ts
  • src/renderer/src/store/slices/database.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/main/database/mysql-driver.test.ts
  • src/renderer/src/components/database/table-data-edit-buffer.test.ts
  • src/renderer/src/components/database/ConnectionForm.tsx
  • src/renderer/src/components/database/table-data-edit-buffer.ts
  • src/main/database/mysql-driver.ts
  • src/renderer/src/components/database/TableDataView.tsx
  • src/renderer/src/store/slices/database.ts
  • src/renderer/src/components/database/SchemaTree.tsx

Comment thread src/renderer/src/components/database/DatabaseWorkspace.tsx
kenzouno1 added 2 commits July 1, 2026 16:21
…failure

Route database:execute reads through the bounded path (Postgres cursor / MySQL row-by-row stream) so a statement that ships without its own LIMIT can no longer materialize the whole result set in the main process.

When ROLLBACK itself fails, release the Postgres client with an eviction flag so a connection that may still hold an open transaction is dropped from the pool instead of being reused.
Wrap JSON.stringify in formatCell so a BigInt (possible inside a JSON column now that the MySQL driver returns big numbers) or a circular reference falls back to String() instead of crashing the grid row render.

Make DbColumnFilter a discriminated union so is-null/is-not-null carry no value and every other operator requires one — a comparison filter can no longer bind an accidental undefined.

@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

Caution

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

⚠️ Outside diff range comments (2)
src/main/database/mysql-query.ts (1)

82-98: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound MySQL row-producing reads before runDirect(). isCursorableRead() only sends SELECT/WITH/VALUES/TABLE through streamBounded(), so SHOW/EXPLAIN/DESCRIBE/DESC still go through conn.query() and can buffer a large result set in memory. Route those reads through the bounded path too.

src/main/database/postgres-query.ts (1)

32-67: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound row-returning writes in the direct path.
fetchBounded still buffers every row for non-cursorable statements, so ... RETURNING can bypass rowLimit and blow up memory.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7e3c63a9-6bc8-4a25-9000-fcbeaccc9450

📥 Commits

Reviewing files that changed from the base of the PR and between 37b42ef and f5ea51c.

📒 Files selected for processing (8)
  • src/main/database/mysql-query.test.ts
  • src/main/database/mysql-query.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts
  • src/renderer/src/components/database/data-grid-cell-format.ts
  • src/renderer/src/components/database/data-grid-column-filter.tsx
  • src/renderer/src/components/database/data-grid-filters.test.ts
  • src/shared/database-types.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/renderer/src/components/database/data-grid-filters.test.ts
  • src/renderer/src/components/database/data-grid-cell-format.ts
  • src/renderer/src/components/database/data-grid-column-filter.tsx
  • src/main/database/postgres-query.test.ts
  • src/main/database/mysql-query.test.ts
  • src/shared/database-types.ts

Comment thread src/main/database/mysql-query.ts
Comment thread src/main/database/postgres-query.ts Outdated
The batch paths ran COMMIT outside the try, so a failing COMMIT skipped rollback and returned a client with unknown transaction state to the pool; the MySQL query/execute paths released a connection whose ROLLBACK had failed. Now every run path rolls back and evicts (pg release(true)) / destroys (MySQL) the connection on any COMMIT or ROLLBACK failure.

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/main/database/mysql-query.ts (1)

136-145: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Poison connections when COMMIT fails, not only when ROLLBACK fails.

A COMMIT rejection can still leave poisoned === false; if the follow-up ROLLBACK succeeds, finally releases the connection back to the pool. Mark it poisoned as soon as COMMIT fails.

As per PR objectives, “if COMMIT or ROLLBACK fails, the affected connection is no longer returned to the pool.”

Proposed fix
       if (!poisoned) {
-        await conn.query('COMMIT')
+        await conn.query('COMMIT').catch((err) => {
+          poisoned = true
+          throw err
+        })
       }
@@
       if (!poisoned) {
-        await conn.query('COMMIT')
+        await conn.query('COMMIT').catch((err) => {
+          poisoned = true
+          throw err
+        })
       }
@@
     try {
       await conn.query('COMMIT')
     } catch (err) {
-      await conn.query('ROLLBACK').catch(() => {
-        poisoned = true
-      })
+      poisoned = true
+      await conn.query('ROLLBACK').catch(() => {})
       throw err
     }

Also applies to: 194-203, 249-257


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e153f2ca-d7c0-437b-a058-ea3331ce8e46

📥 Commits

Reviewing files that changed from the base of the PR and between f5ea51c and 1fd2c1b.

📒 Files selected for processing (4)
  • src/main/database/mysql-query.test.ts
  • src/main/database/mysql-query.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/database/mysql-query.test.ts
  • src/main/database/postgres-query.test.ts
  • src/main/database/postgres-query.ts

The tab wrapper's Enter/Space handler preventDefaults the focused close button's native click, so keyboard users could only re-select the tab. Stop the key event bubbling from the close button so its click fires.
Resolve conflicts in src/main/persistence.ts by combining main's
state-hash write-skip optimization (buildStateToSave/computeStateHash)
with this branch's DB-secrets encryption and file-hardening logic.

@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.

Caution

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

⚠️ Outside diff range comments (2)
src/main/persistence.ts (2)

3511-3525: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Fix the stale async rename vs sync flush race.

flushOrThrow() can bump writeGeneration while an async write is already awaiting rename(). If that stale rename completes after the sync flush, it can overwrite fresher state; the post-rename generation check only avoids updating lastWrittenStateHash, not the file contents.

Proposed recovery after a stale async rename lands
       await rename(tmpFile, dataFile)
       renamed = true
       // Why the gen re-check: a sync flush can interleave during the rename
       // await, write fresher state, and record its own hash. Recording this
       // stale hash over it would make later saves skip against content that
       // is not what the file holds.
       if (this.writeGeneration === gen) {
         this.lastWrittenStateHash = stateHash
+      } else {
+        // Restore the latest in-memory state if this stale async rename landed
+        // after a synchronous flush bumped the generation.
+        this.writeToDiskSync({ force: true })
       }

Also applies to: 3599-3604


3453-3460: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reapply ACLs to rotated backups. rotateBackupsAsync/rotateBackupsSync copy the secret-bearing file into .bak.* without hardening the new snapshot, so those backups can remain readable under default permissions. Harden each backup path after the copy.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 400e045b-d024-414c-ae14-322226610e2b

📥 Commits

Reviewing files that changed from the base of the PR and between 6708c48 and fbe9e25.

📒 Files selected for processing (12)
  • package.json
  • src/main/persistence.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/constants.ts
  • src/shared/types.ts
💤 Files with no reviewable changes (3)
  • src/shared/constants.ts
  • src/shared/types.ts
  • src/renderer/src/web/web-preload-api.ts
✅ Files skipped from review due to trivial changes (1)
  • src/renderer/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/preload/api-types.ts
  • package.json
  • src/preload/index.ts
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/ja.json

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