Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fe1bd63
feat(ui): v3 activity rail, Snapshots workspace, cheaper loads (#373)
huyplb Sep 7, 2026
2f74cca
feat(ui): P2 one compare language — briefing chips, docked Snapshots …
cursoragent Sep 7, 2026
b84c3a6
feat(sql): P3 Last Id paging, catalog table insight, cheaper schema warm
cursoragent Sep 7, 2026
61c311e
feat(access): P4 one session — principal tree, Account/Grants/Effecti…
cursoragent Sep 7, 2026
65a53cd
feat(ui): P5 Home recents, ⌘K palette, Peek Insight tab, opt-in SVG c…
cursoragent Sep 7, 2026
2d153f9
feat(ui): ship the v3 chrome the proposal actually described
cursoragent Sep 7, 2026
5a5b6d0
feat(ui): move database utilities out of the SQL Editor
cursoragent Sep 8, 2026
4691fc0
feat(ui): lock Utilities tools to one workspace credential
cursoragent Sep 8, 2026
559c9d9
feat(ui): put Preferences on the activity rail
cursoragent Sep 8, 2026
ff7e4aa
feat(ui): lock Access panels to one workspace credential
cursoragent Sep 8, 2026
8928415
feat(sql): add a Runs drawer for recent queries
cursoragent Sep 8, 2026
e9dc0c5
feat(ui): land Access on Principals matching the proposal mockups
cursoragent Sep 8, 2026
4247fd2
fix(ui): keep Access Principals sidebar when catalog is unsupported
cursoragent Sep 8, 2026
5813cab
Make the v3 UI phases branch green
huyplb Sep 8, 2026
a7bdffc
feat(ui): ship remaining mockup slices for Grants, Effective, Peek, S…
cursoragent Sep 8, 2026
8ad67f4
Test the Grants stage that is actually rendered
huyplb Sep 8, 2026
9e30235
fix(ui): resolve PR #375 review findings on the v3 phases branch
cursoragent Sep 8, 2026
a7203e0
Merge pull request #386 from tedious-code/fix/v3-flaky-async-queries
cursor[bot] Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/e2e/src/pages/AccessPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ export class AccessPage {
await clickWhen(this.page, `[data-testid="access-tab-${tab}"]`);
}

/** Pick a saved credential in any Access / Database Access <select> by visible name. */
/** Pick a saved credential in any Access / Database Access <select> by visible name.
* The Access workspace uses one chip (`access-connection`); panel-local
* dropdowns are used when a panel still has its own picker. */
async selectConnection(selectTestId: string, nameSubstring: string): Promise<void> {
const select = this.page.locator(`[data-testid="${selectTestId}"]`);
const workspace = this.page.locator('[data-testid="access-connection"]');
const select = (await workspace.isVisible().catch(() => false))
? workspace
: this.page.locator(`[data-testid="${selectTestId}"]`);
await select.waitFor({ state: 'visible', timeout: 10_000 });
const value = await select.evaluate((el, want) => {
const sel = el as HTMLSelectElement;
Expand Down
4 changes: 4 additions & 0 deletions apps/e2e/src/pages/LokeeHistoryPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export class LokeeHistoryPage {
}

async waitForGraph(timeoutMs = 30_000): Promise<void> {
const graphPage = this.page.locator('[data-testid="lokee-weave-page"]');
if (!(await graphPage.isVisible().catch(() => false))) {
await clickWhen(this.page, '[data-testid="lokee-graph-toggle"]');
}
await waitFor(this.page, '[data-testid="lokee-weave-page"]', timeoutMs);
await this.page.locator('[data-testid^="rf-version-"]').first().waitFor({ timeout: timeoutMs });
}
Expand Down
82 changes: 45 additions & 37 deletions apps/e2e/src/pages/SqlEditorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export class SqlEditorPage {
await this.dismissOverlays();
}

/** Open the Database utilities workspace (not the SQL Editor sidebar). */
async openUtilitiesView(): Promise<void> {
await this.dismissOverlays();
await clickWhen(this.page, '[data-testid="view-utilities-btn"]');
await waitFor(this.page, '[data-testid="utilities-view"]', 10_000);
}

async isEditorVisible(): Promise<boolean> {
return this.page.locator('[data-testid="sql-editor-view"]').isVisible();
}
Expand Down Expand Up @@ -430,6 +437,7 @@ export class SqlEditorPage {
localStorage.removeItem('foxschema-sql-sidebar-section-heights');
localStorage.removeItem('foxschema-sql-sidebar-order');
localStorage.removeItem('foxschema-sql-sidebar-order-schema-top-v1');
localStorage.removeItem('foxschema-sql-sidebar-exclusive-v1');
// Width and collapsed state too: a run that inherits a narrow sidebar
// from an earlier one is testing a different layout than the next run,
// which is how a real explorer layout bug surfaced here as an
Expand All @@ -439,30 +447,17 @@ export class SqlEditorPage {
});
}

/** Ensure a sidebar section is expanded (Destinations / Utilities / Schema / …). */
/** Ensure a sidebar section is expanded (Destinations / Schema / …).
* `utilities` / `files` open the Utilities workspace. */
async ensureSidebarSectionOpen(id: string): Promise<void> {
await this.dismissOverlays();
if (id === 'utilities' || id === 'files') {
await this.openUtilitiesView();
return;
}
const section = this.page.locator(`[data-testid="sql-sidebar-${id}"]`);
await section.waitFor({ state: 'visible', timeout: 10_000 });
const toggle = this.page.locator(`[data-testid="sql-sidebar-toggle-${id}"]`);
// Content for utilities is the Index Management button.
const openProbe =
id === 'utilities'
? section.locator('[data-testid="utilities-index-management"]')
: section.locator(`[data-testid="sql-sidebar-${id}"] >> visible=true`);
if (id === 'utilities') {
if (await section.locator('[data-testid="utilities-index-management"]').isVisible().catch(() => false)) {
return;
}
await toggle.click();
await section.locator('[data-testid="utilities-index-management"]').waitFor({
state: 'visible',
timeout: 5_000,
});
return;
}
void openProbe;
// Generic: if toggle says collapsed, click once.
const aria = await toggle.getAttribute('aria-expanded').catch(() => null);
if (aria === 'false') await toggle.click();
}
Expand Down Expand Up @@ -496,15 +491,15 @@ export class SqlEditorPage {
}

async openIndexManagement(): Promise<void> {
await this.ensureSidebarSectionOpen('utilities');
await this.openUtilitiesView();
await clickWhen(this.page, '[data-testid="utilities-index-management"]');
await waitFor(this.page, '[data-testid="index-management-modal"]', 15_000);
}

async openServerInsights(
tab: 'pool' | 'sessions' | 'system' | 'sizes' = 'system'
): Promise<void> {
await this.ensureSidebarSectionOpen('utilities');
await this.openUtilitiesView();
const testId =
tab === 'pool'
? 'utilities-connection-pool'
Expand All @@ -521,62 +516,75 @@ export class SqlEditorPage {
async closeServerInsights(): Promise<void> {
const modal = this.page.locator('[data-testid="server-insights-modal"]');
if (!(await modal.isVisible().catch(() => false))) return;
await modal.locator('button[aria-label="Close"]').click().catch(async () => {
const closeBtn = modal.locator('button[aria-label="Close"]');
if (!(await closeBtn.isVisible().catch(() => false))) return;
await closeBtn.click().catch(async () => {
await this.page.keyboard.press('Escape');
});
await modal.waitFor({ state: 'detached', timeout: 8_000 }).catch(() => undefined);
}

async openDatabaseAccess(): Promise<void> {
await this.dismissOverlays();
await this.ensureSidebarSectionOpen('utilities');
await this.openUtilitiesView();
await clickWhen(this.page, '[data-testid="utilities-database-access"]');
await waitFor(this.page, '[data-testid="db-access-modal"]', 15_000);
}

async closeDatabaseAccess(): Promise<void> {
const modal = this.page.locator('[data-testid="db-access-modal"]');
if (!(await modal.isVisible().catch(() => false))) return;
// The testid is on the backdrop, and the backdrop's own click closes it.
// Going for the header's Close button matches more than one element in this
// subtree, and a strict-mode error there leaves the modal open — which then
// covers the sidebar for every dialect queued behind this one.
await modal.click({ position: { x: 4, y: 4 } }).catch(() => undefined);
await modal.waitFor({ state: 'detached', timeout: 8_000 }).catch(() => undefined);
if (await modal.isVisible().catch(() => false)) {
await this.page.reload();
await this.page.waitForSelector('[data-testid="toolbar"]', { timeout: 30_000 });
const closeBtn = modal.locator('button[aria-label="Close"]');
if (await closeBtn.isVisible().catch(() => false)) {
await closeBtn.click().catch(() => undefined);
await modal.waitFor({ state: 'detached', timeout: 8_000 }).catch(() => undefined);
return;
}
// Overlay (legacy): backdrop click. Workspace pane: leave it docked.
const box = await modal.boundingBox().catch(() => null);
if (box && box.x <= 8 && box.y <= 8) {
await modal.click({ position: { x: 4, y: 4 } }).catch(() => undefined);
await modal.waitFor({ state: 'detached', timeout: 8_000 }).catch(() => undefined);
}
}

async closeIndexManagement(): Promise<void> {
const modal = this.page.locator('[data-testid="index-management-modal"]');
if (!(await modal.isVisible().catch(() => false))) return;
await modal.locator('button[aria-label="Close"]').click().catch(async () => {
const closeBtn = modal.locator('button[aria-label="Close"]');
if (!(await closeBtn.isVisible().catch(() => false))) return;
await closeBtn.click().catch(async () => {
await modal.click({ position: { x: 4, y: 4 } });
});
await modal.waitFor({ state: 'detached', timeout: 8_000 }).catch(() => undefined);
}

async openCloneTable(): Promise<void> {
await this.dismissOverlays();
await this.ensureSidebarSectionOpen('utilities');
await this.openUtilitiesView();
await clickWhen(this.page, '[data-testid="utilities-clone-table"]');
await waitFor(this.page, '[data-testid="clone-table-modal"]', 15_000);
}

async closeCloneTable(): Promise<void> {
const modal = this.page.locator('[data-testid="clone-table-modal"]');
if (!(await modal.isVisible().catch(() => false))) return;
await modal.locator('button[aria-label="Close"]').click().catch(async () => {
const closeBtn = modal.locator('button[aria-label="Close"]');
if (!(await closeBtn.isVisible().catch(() => false))) return;
await closeBtn.click().catch(async () => {
await modal.getByRole('button', { name: /^close$/i }).click();
});
await modal.waitFor({ state: 'detached', timeout: 8_000 }).catch(() => undefined);
}

/** Pick credential in Clone Table / Index Management by visible name substring. */
/** Pick credential in Clone Table / Index Management by visible name substring.
* The Utilities workspace uses one chip (`utilities-connection`); tool-local
* dropdowns are used when a modal still has its own picker. */
async selectUtilityConnection(nameSubstring: string, selectTestId: string): Promise<void> {
const select = this.page.locator(`[data-testid="${selectTestId}"]`);
const workspace = this.page.locator('[data-testid="utilities-connection"]');
const select = (await workspace.isVisible().catch(() => false))
? workspace
: this.page.locator(`[data-testid="${selectTestId}"]`);
await select.waitFor({ state: 'visible', timeout: 10_000 });
const value = await select.evaluate((el, want) => {
const sel = el as HTMLSelectElement;
Expand Down
29 changes: 11 additions & 18 deletions apps/e2e/src/tests/access-assistant-dialects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,23 +132,17 @@ describe.skipIf(configured.length === 0)('Access Assistant (all configured diale
});

/**
* The exact option label for a dialect's saved connection.
*
* The two panels spell it differently: User Management renders
* `name · dialect`, while Permission Builder and Permission Diff render
* `[DIALECT] name`. Playwright matches `selectOption({ label })` as a literal
* string, so one format cannot serve both — and this used to pass a RegExp,
* which the types reject and which matches nothing at runtime, so the
* selection silently never happened at all.
* Access uses one workspace chip (`name · dialect`) for Users, Permission, and Diff.
*/
const usersLabel = (dialect: string) => `${credNameByDialect.get(dialect)!} · ${dialect}`;
const builderLabel = (dialect: string) =>
`[${dialect.toUpperCase()}] ${credNameByDialect.get(dialect)!}`;

async function selectConnection(dialect: string) {
await driver
.locator('[data-testid="user-connection"]')
.selectOption({ label: usersLabel(dialect) });
const label = usersLabel(dialect);
const chip = driver.locator('[data-testid="access-connection"]');
const select = (await chip.isVisible().catch(() => false))
? chip
: driver.locator('[data-testid="user-connection"]');
await select.selectOption({ label });
}

/**
Expand Down Expand Up @@ -231,7 +225,7 @@ describe.skipIf(configured.length === 0)('Access Assistant (all configured diale
await driver.waitForSelector('[data-testid="permission-builder"]', { timeout: 15_000 });
await driver
.locator('[data-testid="access-connection"]')
.selectOption({ label: builderLabel(dialect) });
.selectOption({ label: usersLabel(dialect) });
await driver.locator('[data-testid="access-principal-name"]').fill('report_user');
await fillScope(dialect);

Expand All @@ -256,10 +250,9 @@ describe.skipIf(configured.length === 0)('Access Assistant (all configured diale

await driver.locator('[data-testid="access-tab-diff"]').click();
await driver.waitForSelector('[data-testid="permission-diff"]', { timeout: 15_000 });
const name = credNameByDialect.get(dialect)!;
await driver
.locator('[data-testid="diff-connection"]')
.selectOption({ label: builderLabel(dialect) });
.locator('[data-testid="access-connection"]')
.selectOption({ label: usersLabel(dialect) });
await driver.locator('[data-testid="diff-principal-name"]').fill('report_user');

// The desired-state row offers only the scopes the engine can grant on,
Expand Down Expand Up @@ -346,7 +339,7 @@ describe.skipIf(configured.length === 0)('Access Assistant (all configured diale
await driver.waitForSelector('[data-testid="permission-builder"]', { timeout: 15_000 });
await driver
.locator('[data-testid="access-connection"]')
.selectOption({ label: builderLabel(dialect) });
.selectOption({ label: usersLabel(dialect) });
await driver.locator('[data-testid="access-action"]').getByText('Deny').click();
await driver.locator('[data-testid="access-principal-name"]').fill('report_user');
await driver.locator('[data-testid="access-schema"]').fill('dbo');
Expand Down
6 changes: 5 additions & 1 deletion apps/e2e/src/tests/database-access-dialects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ describe.skipIf(configured.length === 0)('Database Access · User Management', (
*/
async function selectConnection(dialect: string) {
const label = `${credNameByDialect.get(dialect)!} · ${dialect}`;
await driver.locator('[data-testid="user-connection"]').selectOption({ label });
const chip = driver.locator('[data-testid="access-connection"]');
const select = (await chip.isVisible().catch(() => false))
? chip
: driver.locator('[data-testid="user-connection"]');
await select.selectOption({ label });
}

/**
Expand Down
4 changes: 4 additions & 0 deletions apps/e2e/src/tests/dialects/shared-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ export function runDialectFlow(
it.skipIf(skipMigration)('schema history pane records a Lokee snapshot after migrate', async () => {
await clickWhen(driver, '[data-testid="sync-pane-history-btn"]');
await driver.waitForSelector('[data-testid="lokee-weave-view"]', { timeout: 20_000 });
const graphToggle = driver.locator('[data-testid="lokee-graph-toggle"]');
if (await graphToggle.isVisible().catch(() => false)) {
await graphToggle.click();
}
const graph = driver.locator('[data-testid="lokee-weave-page"]');
const hasGraph = await graph
.waitFor({ state: 'visible', timeout: 20_000 })
Expand Down
10 changes: 5 additions & 5 deletions apps/e2e/src/tests/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ describe('App boot', () => {
expect(await driver.locator('[data-testid="toolbar"]').isVisible()).toBe(true);
});

it('puts schema history inside Schema Sync, not a standalone tab', async () => {
it('puts schema history on the Snapshots rail, not a standalone tab', async () => {
expect(await driver.locator('[data-testid="view-lokee-weave-btn"]').count()).toBe(0);
expect(await driver.locator('[data-testid="sync-pane-switcher"]').isVisible()).toBe(true);
expect(await driver.locator('[data-testid="workspace-switcher"]').isVisible()).toBe(true);
await driver.locator('[data-testid="sync-pane-history-btn"]').click();
await driver.waitForSelector('[data-testid="lokee-weave-view"]', { timeout: 15_000 });
expect(await driver.locator('[data-testid="lokee-weave-view"]').isVisible()).toBe(true);
expect(await driver.locator('[data-testid="workspace-switcher"]').count()).toBe(0);
expect(await driver.locator('[data-testid="lokee-history-compare-bar"]').isVisible()).toBe(true);
await driver.locator('[data-testid="sync-pane-compare-btn"]').click();
expect(await driver.locator('[data-testid="workspace-switcher"]').isVisible()).toBe(true);
expect(await driver.locator('[data-testid="lokee-history-compare-bar"]').isVisible()).toBe(true);
await driver.locator('[data-testid="view-sync-btn"]').click();
expect(await driver.locator('[data-testid="sync-pane-switcher"]').isVisible()).toBe(true);
});
});
15 changes: 2 additions & 13 deletions apps/e2e/src/tests/sql-editor-utilities-dialects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,21 +121,14 @@ describe.skipIf(configured.length === 0)('SQL Editor · Utilities (all configure
await sql.closeIndexManagement().catch(() => undefined);
await sql.closeDatabaseAccess().catch(() => undefined);
await sql.closeServerInsights().catch(() => undefined);
const fq = driver.locator('[data-testid="file-query-modal"]');
if (await fq.isVisible().catch(() => false)) {
await fq.locator('button[aria-label="Close"]').click().catch(async () => {
await driver.keyboard.press('Escape');
});
await fq.waitFor({ state: 'detached', timeout: 5_000 }).catch(() => undefined);
}
await sql.dismissOverlays().catch(() => undefined);
});

afterAll(async () => {
if (driver) await quitDriver(driver);
});

it('Utilities sidebar lists every utility entry', async () => {
it('Utilities workspace lists every utility entry', async () => {
await sql.ensureSidebarSectionOpen('utilities');
const section = driver.locator('[data-testid="sql-sidebar-utilities"]');
for (const id of UTILITY_BUTTONS) {
Expand Down Expand Up @@ -317,15 +310,11 @@ describe.skipIf(configured.length === 0)('SQL Editor · Utilities (all configure
});
}

it('Query files utility opens the import modal', async () => {
it('Query files utility opens the import pane', async () => {
await sql.ensureSidebarSectionOpen('utilities');
await driver.locator('[data-testid="utilities-query-files"]').click();
await driver.waitForSelector('[data-testid="file-query-modal"]', { timeout: 10_000 });
expect(await driver.locator('[data-testid="file-query-format"]').isVisible()).toBe(true);
await saveScreenshot(driver, 'utilities-query-files');
await driver.locator('[data-testid="file-query-modal"] button[aria-label="Close"]').click();
await driver
.locator('[data-testid="file-query-modal"]')
.waitFor({ state: 'detached', timeout: 8_000 });
});
});
4 changes: 2 additions & 2 deletions apps/e2e/src/tests/sql-editor-utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ INSERT INTO orders (id, customer_id, note) VALUES

const ready = hasSqlite3();

describe.skipIf(!ready)('SQL Editor · Utilities + Clone Table (SQLite)', () => {
describe.skipIf(!ready)('Utilities workspace + Clone Table (SQLite)', () => {
let driver: Page;
let app: AppPage;
let sql: SqlEditorPage;
Expand Down Expand Up @@ -107,7 +107,7 @@ describe.skipIf(!ready)('SQL Editor · Utilities + Clone Table (SQLite)', () =>
rmSync(DIR, { recursive: true, force: true });
});

it('sidebar Utilities lists Index Management and Clone Table', async () => {
it('Utilities workspace lists Index Management and Clone Table', async () => {
await sql.ensureSidebarSectionOpen('utilities');
const utilities = driver.locator('[data-testid="sql-sidebar-utilities"]');
expect(await utilities.locator('[data-testid="utilities-index-management"]').isVisible()).toBe(
Expand Down
Loading
Loading