feat(ui): v3 chrome — rail, chips, Preferences, Access Principals - #385
Conversation
* perf: reuse access catalogs and stop N+1 schema/history loads Cache POST /schema/db-access for 60s so Access tabs share one catalog probe, invalidate after GRANT/REVOKE, and stop warming every SQL destination on editor mount (the explorer connection already loads). Snapshot metadata updates read the one row that changed instead of 500 versions, inspect reuses that list for growth, and Lokee GET routes require schema.browse. SQL sidebar opens one section at a time so first paint is a single panel. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com> * feat(ui): v3 activity rail and timeline-first Snapshots Move workspace switching to a left rail (Sync, SQL, Access, Snapshots) and drop the stacked toolbar switcher. Schema history is its own Snapshots view with a version timeline by default; React Flow loads only when Graph is toggled. Compare keeps a +/~/- briefing from the existing DTO, and Access control stays on admin.users / admin.roles. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
…pane, SVG ticks Sync and Snapshots now share + / ~ / − chips. Version list loads ADD/MODIFY/DELETE counts in one grouped query. Timeline click docks the same SchemaBlueprint compare beside the list; React Flow stays opt-in. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Next page uses a unique ORDER BY keyset when the last row is in cache; OFFSET remains the fallback. Insight probes read pg_stats / STATISTICS / sqlite_stat1 and fail tests if SQL contains COUNT(DISTINCT). Schema explorer loads TABLE|VIEW|MQT first and fetches routines only when those groups open. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
| try { | ||
| await work; | ||
| } finally { | ||
| if (schemaLoadInflight.get(inflightKey) === work) schemaLoadInflight.delete(inflightKey); |
…ve, generate-only Access → Permission is now one cached catalog: a principal tree plus Account, Grants, and Effective stages. Confirm copies SQL or opens the SQL Editor; GRANT/REVOKE execute stays in Database Access. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
…harts Home opens from the rail logo and lists Zustand recents/connections with no introspect. ⌘K jumps to workspaces, connections, and recents. Data Peek fetches catalog insight only when that tab is selected. Result grids with a label + number shape can opt into an SVG bar chart. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_81c9e260-d010-467a-b7a9-eae5111f1c33) |
Replace stacked Original/Target cards with connection chips, land Compare on Diff Briefing, turn the SQL sidebar into a 48px section rail with Run · N destination chips, open Home first, and add Snapshots briefing plus a stacked change chart. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_917d3872-7762-4453-821e-f5a34df94734) |
Give Index Management, Clone Table, Server Insights, Query files, and DB users their own Utils rail workspace so DBA tools are not buried in the SQL sidebar. Tools dock as panes (same e2e testids) and SQL keeps destinations, schema, bookmarks, variables, and secrets. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_31a47bf7-a948-475a-8603-602b91d1a72c) |
Pick a database once at the top of Utilities. Index, Clone, Insights, and DB access inherit that chip instead of asking again inside each pane. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Settings is a workspace now, titled Preferences, with Appearance / Database / Updates / Security tabs. The profile menu and ⌘K open the same view instead of a modal named Appearance. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Pick a database once on the Access chrome. User Management, Permission, and Diff inherit that chip instead of asking again in each pane. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Recent runs sit on the SQL toolbar as a drawer instead of only inside the Bookmarks sidebar tab. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_bd70aef9-d233-4a54-aea2-d38d8f934050) |
Access opens on the principals sidebar with Account | Grants | Effective. Users/Roles filter, Add user, and counts match the Access chrome designs; User Management and Diff stay as secondary tabs for e2e. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_898174db-71c5-4af2-bc3a-c2a25c0745ba) |
SQLite and other engines without a GRANT catalog still show the Principals chrome (filter, Users/Roles, Add user). The unsupported message moves into the main pane instead of hiding the sidebar. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_eb069dcd-bf84-4018-87df-f89f68b8c067) |
Four unrelated failures, three of them real.
**Access no longer opens on Users.** The default section moved to
`permission`, and 28 tests in GeneratedPassword.test.tsx render AccessView and
reach straight for `user-connection`. They pass now by clicking through to the
Users tab, which is also the honest fix: the default on main existed to keep
these tests passing — its own comment said so — and a product default should
not be chosen by a test.
**A stray `});`** in sql-editor-utilities-dialects.test.ts closed the describe
twice and left the file unparseable. ESLint reported it as
"Parsing error: Declaration or statement expected", which is why the whole
lint job failed rather than one rule.
**The IDENT regex** in sql-order-by.ts crossed four delimiter styles with an
optional second part, nesting a quantifier inside an optional group —
`security/detect-unsafe-regex`, fairly. Replaced by scanning for the single
separating dot. Verified as behaviour-preserving rather than assumed: a
differential harness ran both implementations over 34 inputs (quoted,
backticked, bracketed, bare, qualified, malformed, unterminated) and they
agree on every one. The first attempt differed on `db . orders`, because
trimming each part accepted spaces the regex rejected; that trim is gone, so
this is a lint fix and not a quiet parser change.
**The route contract** tripwire fired: the table gained
`POST /api/schema/table-insight` but the count stayed at 80. The route is
real and guarded by dbaUtilityLimiter + requirePermissions('utility.access'),
so the count moves to 81 with a note saying which route did it.
Also `commandPalette.ts` -> `commandPaletteEvent.ts`. It sat beside
`CommandPalette.tsx`, and on a case-insensitive filesystem both specifiers
resolved to the same file — the component imported as `undefined` and all five
palette tests died on "Element type is invalid". Linux CI resolves exactly and
never saw it, so this was macOS-only and invisible to the pipeline.
Repo-wide: eslint 0 errors, tsc clean, 3616 passed / 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_d9578847-31fb-4712-9f2b-bd2102fd278f) |
…napshots, Migrate Wire Access Grants desired matrix with presets and GRANT SQL, Effective ALLOW/DENY why-stacks, Peek Insight summary cards, Snapshots Revert SQL tab label, and Data migrate Preview SQL plus Safe-mode Apply confirm. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_24d9051c-e984-4a68-9d28-86184dc60935) |
a7bdffc swapped DbAccessPermissionSections for the new AccessGrantsStage in AccessPermissionPanel but left the tests driving the old component, so three of them walked a UI that no longer exists — `db-access-expand-table`, `db-access-permission-sections`. They were reported as flaky rather than broken because they read with `getByTestId` immediately after a click, and the stage loads its own schema objects. A synchronous read races the load: sometimes the old markup was still mounted, sometimes not. Switching to `findByTestId` turned the same failure deterministic, which is what exposed the swap. Rewritten against the stage as it is now: pick a preset, read the generated SQL, copy it or hand it to the editor. Both paths still assert `runAccessSql` was never called, which is the guarantee the screen exists to keep — the stage says so itself: "Fox Schema generates GRANT/REVOKE SQL — it does not apply it." The preset is `read-write`, not `read-only`, because the fixture already grants alice SELECT on public.orders. Read-only therefore matches the catalog and correctly generates nothing — I had the buttons' disabled state down as a bug until the SQL panel said "Desired matrix matches the live catalog". That case is now a test of its own, since offering a GRANT that changes nothing would still be a write against production. Stable over 6 consecutive runs; suite 3617 passed / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lazy-warm SQL schemas on completion and Data migrate, let Refresh force db-access past the 60s TTL, drop matching inflight on invalidate so stale catalogs cannot repopulate, require containerGrowth's loaded versions, and re-indent the Lokee inspect/compare handlers. Co-authored-by: huy.phan9 <huyplb@users.noreply.github.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_d036eb21-0c16-4694-882a-46829f3173da) |
test: drive the Grants stage that is actually rendered
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_97a27c08-e2cf-4d19-8394-e17d08b3c588) |
Summary
v3 chrome plus mockup slices (activity rail, Home, Sync chips, Utils workspace, Preferences, Access Principals-first, SQL Runs drawer, Grants matrix, Effective why-stack, Peek Insight cards, Snapshots Revert SQL, Data migrate Preview/Safe mode).
Also addresses PR #375 review findings
#375 (
cursor/main-v3-ui-dbb9) is the earlier reopened rail PR — open but CONFLICTING withmainand superseded by this branch. Its listed findings are fixed here:fetchDbAccess({ force: true })on Refresh / Reload / Load cataloginvalidateCacheclears matchinginflight; mid-flight results do not repopulate TTL cachecontainerGrowth(loadedVersions)required (no dead listVersions fallback)Test plan
cd apps/web && npx tsc --noEmitPeek Insight cards
Snapshots Revert SQL tab
mockup-slices-peek-snapshots-demo.mp4
To show artifacts inline, enable in settings.