diff --git a/apps/e2e/src/pages/AppPage.ts b/apps/e2e/src/pages/AppPage.ts index 41212692..eb5006fa 100644 --- a/apps/e2e/src/pages/AppPage.ts +++ b/apps/e2e/src/pages/AppPage.ts @@ -36,9 +36,36 @@ export class AppPage { await waitFor(this.page, '[data-testid="toolbar"]', 20_000); } + /** + * Put the Sync workspace on screen. + * + * The app lands on Home now, not Sync, so the compare controls are not + * mounted when a spec starts. Reaching straight for them failed as a 15s + * `page.click` timeout on every dialect at once — which reads like the app is + * broken rather than like the test is on the wrong screen. + * + * Idempotent: already on Sync, the rail button is a no-op. + */ + async gotoSync(): Promise { + const rail = this.page.locator('[data-testid="view-sync-btn"]'); + if (await rail.isVisible().catch(() => false)) { + await clickWhen(this.page, '[data-testid="view-sync-btn"]'); + } + // Two conditions, not one. TopToolbar gates the connection chips on + // `activeView === 'sync' && syncPane === 'compare'`, and Sync can open on + // the Snapshots pane — so selecting the workspace alone leaves the compare + // controls unmounted and every click on them times out. + const compare = this.page.locator('[data-testid="sync-pane-compare-btn"]'); + if (await compare.isVisible().catch(() => false)) { + await clickWhen(this.page, '[data-testid="sync-pane-compare-btn"]'); + } + await waitFor(this.page, '[data-testid="source-config-btn"]', 15_000); + } + // ── Source side ───────────────────────────────────────────────────────── async openSourceModal(): Promise { + await this.gotoSync(); await clickWhen(this.page, '[data-testid="source-config-btn"]'); await waitFor(this.page, '[data-testid="conn-modal"]'); } @@ -54,6 +81,7 @@ export class AppPage { // ── Target side ───────────────────────────────────────────────────────── async openTargetModal(): Promise { + await this.gotoSync(); await clickWhen(this.page, '[data-testid="target-config-btn"]'); await waitFor(this.page, '[data-testid="conn-modal"]'); } diff --git a/apps/web/src/frontend/App.tsx b/apps/web/src/frontend/App.tsx index 94e565b2..93d3de6f 100644 --- a/apps/web/src/frontend/App.tsx +++ b/apps/web/src/frontend/App.tsx @@ -1,7 +1,10 @@ import React, { Suspense, lazy, useEffect } from 'react'; import { TopToolbar } from '@/app/shell/TopToolbar'; import { ActivityRail } from '@/app/shell/ActivityRail'; -import { SchemaTreePanel } from '@/features/sql-editor'; +// Deep import, not the feature barrel: the barrel re-exports the editor, +// which pulls Monaco (2.6 MB) into the eager graph and makes the lazy() below +// decorative. Rolldown said so — INEFFECTIVE_DYNAMIC_IMPORT. +import { SchemaTreePanel } from '@/features/sql-editor/components/SchemaTreePanel'; import { ObjectDetailPanel } from '@/features/object-detail'; import { ErrorBoundary } from '@/app/shell/ErrorBoundary'; import { LoadingScreen } from '@/app/shell/LoadingScreen'; @@ -21,7 +24,9 @@ const AccessView = lazy(() => import('@/features/access').then((m) => ({ default: m.AccessView })) ); const SqlEditorView = lazy(() => - import('@/features/sql-editor').then((m) => ({ default: m.SqlEditorView })) + import('@/features/sql-editor/components/SqlEditorView').then((m) => ({ + default: m.SqlEditorView, + })) ); const UtilitiesView = lazy(() => import('@/features/utilities').then((m) => ({ default: m.UtilitiesView })) diff --git a/apps/web/src/frontend/app/shell/ConnectionChips.tsx b/apps/web/src/frontend/app/shell/ConnectionChips.tsx index 82fa88fc..332e85ff 100644 --- a/apps/web/src/frontend/app/shell/ConnectionChips.tsx +++ b/apps/web/src/frontend/app/shell/ConnectionChips.tsx @@ -68,7 +68,12 @@ export function ConnectionChip({ return (
{label} @@ -79,7 +84,14 @@ export function ConnectionChip({ value={selectedId ?? ''} onChange={(e) => e.target.value && onSelect(e.target.value)} title="Saved connections" - className="min-w-0 max-w-[11rem] shrink-0 truncate rounded-full border border-slate-700/60 bg-slate-950 px-2 py-0.5 text-[11px] text-slate-200 accent-focus focus:outline-none" + // No shrink-0: the chip itself is `min-w-0 flex-1`, so a crowded toolbar + // collapses its box while its children keep their intrinsic width. The + // label and the buttons genuinely cannot shrink, so with this select + // refusing too, ~210px of content sat in a 95px box and spilled across + // the toolbar — far enough that the "Same DB" pill covered the edit + // button and swallowed the click. This select already truncates, so it + // is the one that can give ground. + className="min-w-0 max-w-[11rem] truncate rounded-full border border-slate-700/60 bg-slate-950 px-2 py-0.5 text-[11px] text-slate-200 accent-focus focus:outline-none" > {connections.map((c) => ( diff --git a/apps/web/src/frontend/app/shell/TopToolbar.tsx b/apps/web/src/frontend/app/shell/TopToolbar.tsx index 89408469..e2d5f149 100644 --- a/apps/web/src/frontend/app/shell/TopToolbar.tsx +++ b/apps/web/src/frontend/app/shell/TopToolbar.tsx @@ -7,17 +7,17 @@ import { ArrowRight, ArrowLeftRight, RefreshCw, AlertCircle, Zap, Settings, KeyR import ProfileMenuDefault, { ProfileMenu as ProfileMenuNamed } from './ProfileMenu'; import { CredentialManager } from '@/features/connections'; import { MigrationHistory } from '@/features/migrations'; -import { TYPE_META, TYPE_ORDER } from '@/features/sql-editor'; +import { TYPE_META, TYPE_ORDER } from '@/features/sql-editor/components/SchemaTreePanel'; import type { DbObjectType } from '@/shared/lib/types'; import { connectionNeedsSecret } from '@/shared/lib/provider-settings'; import { schemaCompareBlocker } from '@/shared/lib/dialect-features'; import { ConnectionModal } from '@/features/connections'; import { PasswordInput } from '@/shared/components/PasswordInput'; import { useAuthStore } from '@/app/store/authStore'; -import { captureSchema } from '@/features/lokee-weave'; +import { captureSchema } from '@/features/lokee-weave/api/lokeeApi'; import { toast } from '@/app/store/toastStore'; import { getSessionPassword, setSessionPassword } from '@/shared/lib/sessionPasswords'; -import { HistoryCompareBar } from '@/features/lokee-weave'; +import { HistoryCompareBar } from '@/features/lokee-weave/components/HistoryCompareBar'; import { BrowseBar } from '@/features/object-detail'; import { ActivityIndicator } from './ActivityIndicator'; import { DiffBriefingChips } from '@/features/schema-diff'; diff --git a/apps/web/src/frontend/features/access/components/AccessGrantsStage.tsx b/apps/web/src/frontend/features/access/components/AccessGrantsStage.tsx index d53fe116..7e01deec 100644 --- a/apps/web/src/frontend/features/access/components/AccessGrantsStage.tsx +++ b/apps/web/src/frontend/features/access/components/AccessGrantsStage.tsx @@ -27,6 +27,7 @@ import { type DbAccessConfirmRequest, } from './DbAccessPermissionSections'; import type { DbPrincipal } from '@foxschema/sql'; +import { sectionLabelCls } from '@/shared/components/surfaces'; const PRESET_LABEL: Record, string> = { 'read-only': 'Read only', @@ -184,7 +185,7 @@ export const AccessGrantsStage: React.FC<{ ) : ( <>
- + Presets {(Object.keys(PRESET_LABEL) as Exclude[]).map((p) => ( diff --git a/apps/web/src/frontend/features/access/components/DbAccessPermissionSections.tsx b/apps/web/src/frontend/features/access/components/DbAccessPermissionSections.tsx index 294ee06a..44a6a0e8 100644 --- a/apps/web/src/frontend/features/access/components/DbAccessPermissionSections.tsx +++ b/apps/web/src/frontend/features/access/components/DbAccessPermissionSections.tsx @@ -44,6 +44,7 @@ import { type PermissionRequest, } from '@/features/access/lib/access'; import { useAllSchemaObjects } from '@/features/access/lib/useAllSchemaObjects'; +import { sectionLabelCls } from '@/shared/components/surfaces'; type ActionMode = 'grant' | 'revoke'; @@ -435,7 +436,7 @@ export const DbAccessPermissionSections: React.FC = ({ return (
-
+
Permissions
diff --git a/apps/web/src/frontend/features/access/components/PermissionBuilder.tsx b/apps/web/src/frontend/features/access/components/PermissionBuilder.tsx index 7aaf7122..61e451ef 100644 --- a/apps/web/src/frontend/features/access/components/PermissionBuilder.tsx +++ b/apps/web/src/frontend/features/access/components/PermissionBuilder.tsx @@ -27,6 +27,7 @@ import { useAllSchemaObjects } from '../lib/useAllSchemaObjects'; import { useSyncStore } from '@/app/store/useSyncStore'; import { dialectFeatureReason } from '@/shared/lib/dialect-features'; import type { AccessPrincipalDraft } from '../lib/access-draft'; +import { sectionLabelCls } from '@/shared/components/surfaces'; const PRESET_LABEL: Record = { 'read-only': 'Read only', @@ -450,7 +451,7 @@ export const PermissionBuilder: React.FC<{ * have in mind. */}
- + Apply to all {(Object.keys(PRESET_LABEL) as AccessPreset[]) diff --git a/apps/web/src/frontend/features/admin/components/AdminAccessPanel.tsx b/apps/web/src/frontend/features/admin/components/AdminAccessPanel.tsx index 290c61aa..b5720d2b 100644 --- a/apps/web/src/frontend/features/admin/components/AdminAccessPanel.tsx +++ b/apps/web/src/frontend/features/admin/components/AdminAccessPanel.tsx @@ -33,6 +33,7 @@ import { import { useAuthStore } from '@/app/store/authStore'; import { PasswordInput } from '@/shared/components/PasswordInput'; import { AccessReport } from '@/features/access/components/AccessReport'; +import { sectionLabelCls } from '@/shared/components/surfaces'; type Tab = 'users' | 'roles' | 'users-roles'; @@ -610,7 +611,7 @@ export const AdminAccessPanel: React.FC<{ open: boolean; onClose: () => void }> ) : ( )} - + {group} {/* The count is the point of collapsing: it answers "what diff --git a/apps/web/src/frontend/features/auth/components/SignupWizard.tsx b/apps/web/src/frontend/features/auth/components/SignupWizard.tsx index 3fb4a6c0..3ced620a 100644 --- a/apps/web/src/frontend/features/auth/components/SignupWizard.tsx +++ b/apps/web/src/frontend/features/auth/components/SignupWizard.tsx @@ -10,6 +10,7 @@ import React, { useState } from 'react'; import { Loader2, AlertCircle, Mail, Sparkles } from 'lucide-react'; import { submitSignup, skipSignup } from '../api/signupApi'; import { Brand } from '@/app/shell/Brand'; +import { sectionLabelCls } from '@/shared/components/surfaces'; /** * One-time, skippable first-run prompt: collect a subscriber email when the @@ -76,7 +77,7 @@ export const SignupWizard: React.FC<{ onDone: () => void }> = ({ onDone }) => {