Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions apps/e2e/src/pages/AppPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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<void> {
await this.gotoSync();
await clickWhen(this.page, '[data-testid="source-config-btn"]');
await waitFor(this.page, '[data-testid="conn-modal"]');
}
Expand All @@ -54,6 +81,7 @@ export class AppPage {
// ── Target side ─────────────────────────────────────────────────────────

async openTargetModal(): Promise<void> {
await this.gotoSync();
await clickWhen(this.page, '[data-testid="target-config-btn"]');
await waitFor(this.page, '[data-testid="conn-modal"]');
}
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/frontend/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 }))
Expand Down
16 changes: 14 additions & 2 deletions apps/web/src/frontend/app/shell/ConnectionChips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ export function ConnectionChip({
return (
<div
data-testid={`connection-chip-${side}`}
className={`flex min-w-0 max-w-xl flex-1 items-center gap-1.5 rounded-full border px-2 py-1 ${tone.ring}`}
// A width floor, not min-w-0. The toolbar is flex-wrap, so a chip that
// cannot fit its label, picker and buttons should push itself onto the
// next row rather than collapse to 95px and paint its controls across its
// neighbours — which is what put the edit button under the "Same DB" pill
// and made it unclickable.
className={`flex min-w-[15rem] max-w-xl flex-1 items-center gap-1.5 rounded-full border px-2 py-1 ${tone.ring}`}
>
<span className={`shrink-0 text-[10px] font-bold uppercase tracking-wider ${tone.label}`}>
{label}
Expand All @@ -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"
>
<option value="">— Saved —</option>
{connections.map((c) => (
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/frontend/app/shell/TopToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Exclude<AccessPreset, 'custom'>, string> = {
'read-only': 'Read only',
Expand Down Expand Up @@ -184,7 +185,7 @@ export const AccessGrantsStage: React.FC<{
) : (
<>
<div className="flex flex-wrap items-center gap-1.5" data-testid="access-grants-presets">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Presets
</span>
{(Object.keys(PRESET_LABEL) as Exclude<AccessPreset, 'custom'>[]).map((p) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -435,7 +436,7 @@ export const DbAccessPermissionSections: React.FC<Props> = ({
return (
<div className="space-y-2" data-testid="db-access-permission-sections">
<div className="flex items-center justify-between gap-2">
<div className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<div className={sectionLabelCls}>
Permissions
</div>
<span className="text-[10px] text-slate-500 truncate">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccessPreset, string> = {
'read-only': 'Read only',
Expand Down Expand Up @@ -450,7 +451,7 @@ export const PermissionBuilder: React.FC<{
* have in mind.
*/}
<div className="mb-3 flex flex-wrap items-center gap-1.5">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Apply to all
</span>
{(Object.keys(PRESET_LABEL) as AccessPreset[])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -610,7 +611,7 @@ export const AdminAccessPanel: React.FC<{ open: boolean; onClose: () => void }>
) : (
<ChevronRight className="w-3 h-3 shrink-0 text-slate-500" />
)}
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
{group}
</span>
{/* The count is the point of collapsing: it answers "what
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -76,7 +77,7 @@ export const SignupWizard: React.FC<{ onDone: () => void }> = ({ onDone }) => {

<div className="px-6 py-5 flex flex-col gap-3.5">
<label className="block">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Email
</span>
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import {
type MigrationRunStatus,
} from '../api/migrationApi';

const SqlEditor = lazy(() => import('@/features/sql-editor').then((m) => ({ default: m.SqlEditor })));
const SqlEditor = lazy(() =>
import('@/features/sql-editor/components/SqlEditor').then((m) => ({ default: m.SqlEditor }))
);

interface Props {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ import { DependencyWarningDialog } from '@/features/object-detail/components/Dep
import { ValidationWarningsDialog } from '@/features/object-detail/components/ValidationWarningsDialog';
import { CrossDialectReadinessDialog } from '@/features/object-detail/components/CrossDialectReadinessDialog';
// Monaco is heavy — load it only when a SQL surface is actually shown
const SqlEditor = lazy(() => import('@/features/sql-editor').then((m) => ({ default: m.SqlEditor })));
const SqlDiffEditor = lazy(() => import('@/features/sql-editor').then((m) => ({ default: m.SqlDiffEditor })));
const SqlEditor = lazy(() =>
import('@/features/sql-editor/components/SqlEditor').then((m) => ({ default: m.SqlEditor }))
);
const SqlDiffEditor = lazy(() =>
import('@/features/sql-editor/components/SqlEditor').then((m) => ({ default: m.SqlDiffEditor }))
);

const EditorFallback: React.FC = () => (
<div className="flex-1 flex items-center justify-center text-slate-500 text-xs gap-2">
Expand Down
14 changes: 10 additions & 4 deletions apps/web/src/frontend/features/sql-editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ export { scrubRemovedFileConnections } from './lib/fileQueryEditorHelpers';
export { getCaretOffset, getSelectedSql, insertAtCursor } from './lib/sqlEditorBridge';
export type { SchemaCacheEntry } from './lib/sqlEditorBridge';
export { dialectFkConstraintSupport, dialectIndexSupport, executableSqlStatements, findInboundForeignKeyTables, generateCloneTableSql } from './lib/tableBlueprintSql';
export { SqlDiffEditor } from './components/SqlEditor';
export { SqlEditor } from './components/SqlEditor';
export { SqlEditorView } from './components/SqlEditorView';
export { FileImportsPanel } from './components/FileImportsPanel';
// The editor views are deliberately NOT re-exported here.
//
// A barrel is one module: re-exporting them made every consumer of any symbol
// above pull Monaco (2.6 MB) into the eager graph, which is what turned the
// `lazy()` calls in App.tsx and elsewhere into decoration. Rolldown had been
// saying so all along — INEFFECTIVE_DYNAMIC_IMPORT.
//
// Every consumer of these loads them through `lazy(() => import(...))`, so
// importing the component module directly costs them nothing and keeps the
// editor out of first paint.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { useSqlEditorStore } from '@/app/store/useSqlEditorStore';
import { useAuthStore } from '@/app/store/authStore';
import { PROVIDER_SETTINGS, connectionNeedsSecret } from '@/shared/lib/provider-settings';
import { DbAccessPermissionSections } from '@/features/access/components/DbAccessPermissionSections';
import { sectionLabelCls } from '@/shared/components/surfaces';

interface Props {
open: boolean;
Expand Down Expand Up @@ -267,7 +268,7 @@ export const DatabaseAccessModal: React.FC<Props> = ({
<div className="flex flex-wrap items-end gap-2">
{!lockedConnectionId && (
<label className="flex flex-col gap-1 min-w-[14rem] flex-1">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Credential
</span>
<select
Expand Down Expand Up @@ -300,7 +301,7 @@ export const DatabaseAccessModal: React.FC<Props> = ({
)}
{needsPassword && (
<label className="flex flex-col gap-1 min-w-[10rem]">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Session password
</span>
<div className="flex gap-1">
Expand Down Expand Up @@ -600,7 +601,7 @@ export const DatabaseAccessModal: React.FC<Props> = ({
data-testid="db-access-grant-form"
className="rounded-lg border border-slate-800 p-3 space-y-2"
>
<div className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<div className={sectionLabelCls}>
Grant
</div>
{!canGrant && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
type IndexMgmtSort,
type IndexMgmtSortKey,
} from '@/features/utilities/lib/indexManagementGrid';
import { sectionLabelCls } from '@/shared/components/surfaces';

interface Props {
open: boolean;
Expand Down Expand Up @@ -771,7 +772,7 @@ export const IndexManagementModal: React.FC<Props> = ({
<div className="flex flex-wrap items-end gap-2">
{!lockedConnectionId && (
<label className="flex flex-col gap-1 min-w-[14rem] flex-1">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Credential
</span>
<select
Expand Down Expand Up @@ -802,7 +803,7 @@ export const IndexManagementModal: React.FC<Props> = ({
)}
{needsPassword && (
<label className="flex flex-col gap-1 min-w-[10rem]">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Session password
</span>
<div className="flex gap-1">
Expand Down Expand Up @@ -871,7 +872,7 @@ export const IndexManagementModal: React.FC<Props> = ({
className="flex flex-col gap-1 min-w-[12rem] flex-1"
data-testid={embedded ? 'server-insights-size-filter' : undefined}
>
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Filter table / index
</span>
<input
Expand All @@ -883,7 +884,7 @@ export const IndexManagementModal: React.FC<Props> = ({
/>
</label>
<label className="flex flex-col gap-1 w-36">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Min frag %
</span>
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { useSyncStore } from '@/app/store/useSyncStore';
import { useSqlEditorStore } from '@/app/store/useSqlEditorStore';
import { PROVIDER_SETTINGS, connectionNeedsSecret } from '@/shared/lib/provider-settings';
import { IndexManagementModal } from './IndexManagementModal';
import { sectionLabelCls } from '@/shared/components/surfaces';

export type ServerInsightsTab = DbaUtilityKind;

Expand Down Expand Up @@ -206,7 +207,7 @@ export const ServerInsightsModal: React.FC<Props> = ({
<div className="flex flex-wrap items-end gap-2 border-b border-slate-800 bg-slate-950/30 px-5 py-3 shrink-0">
{!lockedConnectionId && (
<label className="flex min-w-[14rem] flex-1 flex-col gap-1">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Credential
</span>
<select
Expand Down Expand Up @@ -235,7 +236,7 @@ export const ServerInsightsModal: React.FC<Props> = ({
)}
{needsPassword && (
<label className="flex flex-col gap-1 min-w-[10rem]">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Session password
</span>
<div className="flex gap-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DatabaseAccessModal } from './DatabaseAccessModal';
import { FileQueryModal } from './FileQueryModal';
import { IndexManagementModal } from './IndexManagementModal';
import { ServerInsightsModal, type ServerInsightsTab } from './ServerInsightsModal';
import { sectionLabelCls } from '@/shared/components/surfaces';

export type UtilityTool =
| 'indexes'
Expand Down Expand Up @@ -239,7 +240,7 @@ export const UtilitiesView: React.FC = () => {
<p className="mt-0.5 text-[11px] text-slate-500">{active.blurb}</p>
</div>
<label className="flex min-w-[16rem] max-w-md flex-1 flex-col gap-1">
<span className="text-[10px] font-bold uppercase tracking-wide text-slate-500">
<span className={sectionLabelCls}>
Credential
</span>
<select
Expand Down
Loading
Loading