From c1c428f515d1bf3249b177134dab571245cd2db3 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 9 Aug 2026 11:01:42 +0545 Subject: [PATCH 01/49] feat(connections): search the saved list, and make Cmd+R actually rescan The saved rail was a plain scroll: past a dozen connections, finding one meant reading every row. A filter appears once the list is long enough to need one (>5) and matches name, engine, database, host, file path and libSQL URL, so any of the things shown on a row will find it. Escape clears the filter before it closes the dialog. Cmd/Ctrl+R was already bound but never fired. It listened on the bubble via svelte:window, and the dialog is full of inputs and a bits-ui overlay that stop propagation before it gets there. Moved to the capture phase, which also guarantees the preventDefault lands before the webview treats Cmd+R as 'reload the app'. --- src/lib/components/ConnectionModal.svelte | 95 +++++++++++++++++++---- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/src/lib/components/ConnectionModal.svelte b/src/lib/components/ConnectionModal.svelte index 4ace24a9..91c28094 100644 --- a/src/lib/components/ConnectionModal.svelte +++ b/src/lib/components/ConnectionModal.svelte @@ -221,6 +221,18 @@ (b.lastConnectedAt ?? 0) - (a.lastConnectedAt ?? 0); let saved = $state(loadSavedConnections().sort(byLastConnected)); + /** Filter over the saved rail. Matches the things you would recognise a + * connection by — its name, its engine, and where it points. */ + let savedQuery = $state(""); + const savedMatches = $derived.by(() => { + const q = savedQuery.trim().toLowerCase(); + if (!q) return saved; + return saved.filter((c) => + [c.name, c.type, c.database, c.host, c.filePath, c.libsqlUrl] + .filter(Boolean) + .some((v) => String(v).toLowerCase().includes(q)), + ); + }); let lastId = $state(getLastConnectionId()); let editingId = $state(/** @type {string|null} */ (null)); let connecting = $state(/** @type {string|null} */ (null)); @@ -1121,6 +1133,21 @@ ); let localPhase = $state(/** @type {'idle'|'scanning'|'done'} */ ("idle")); + /** + * ⌘R / Ctrl+R — rescan. Reloads the saved list as well as the local scan, so + * one key means "show me what is actually there now". + * @param {KeyboardEvent} e + */ + function onRefreshKey(e) { + if (!open) return; + if (!(e.ctrlKey || e.metaKey) || e.altKey || e.shiftKey) return; + if (e.key.toLowerCase() !== "r") return; + e.preventDefault(); + e.stopPropagation(); + saved = loadSavedConnections().sort(byLastConnected); + void refreshLocal(); + } + async function refreshLocal() { localPhase = "scanning"; // Discovery is a convenience on both sides. A failure means "nothing found", @@ -1603,19 +1630,16 @@ + { if (!open) return; - if ( - (e.ctrlKey || e.metaKey) && - !e.altKey && - !e.shiftKey && - e.key.toLowerCase() === "r" - ) { - e.preventDefault(); - saved = loadSavedConnections().sort(byLastConnected); - void refreshLocal(); - } if ( (e.ctrlKey || e.metaKey) && !e.altKey && @@ -1897,7 +1921,7 @@ {#if saved.length > 0} {saved.length}{savedQuery ? `${savedMatches.length}/${saved.length}` : saved.length} {/if} + {/if} + + + {/if} +
- {#if saved.length > 0} + {#if savedQuery && savedMatches.length === 0} +

+ No connection matches “{savedQuery}”. +

+ {:else if saved.length > 0}
- {#each saved as conn, i (conn.id)} + {#each savedMatches as conn, i (conn.id)} {@const isSel = conn.id === editingId} {@const busy2 = connecting === conn.id} {@const cid = From 862b4abe977b5afe9edf0afee614dd33a740cf12 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 9 Aug 2026 11:01:52 +0545 Subject: [PATCH 02/49] fix(settings): move anonymous usage data out of Agent into General It sat under Agent, between two AI switches, which read as if it were about the AI features. It covers the whole app, so it belongs in General under its own Privacy heading. --- src/lib/components/SettingsDialog.svelte | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/components/SettingsDialog.svelte b/src/lib/components/SettingsDialog.svelte index 2c2112a5..32d2113c 100644 --- a/src/lib/components/SettingsDialog.svelte +++ b/src/lib/components/SettingsDialog.svelte @@ -631,14 +631,6 @@ toggleAgentWebAccess, )} {/if} - {#if show('Anonymous usage data', 'Help decide what to build next')} - {@render switchRow( - 'Anonymous usage data', - 'Sends which features you use, how often, the app version and your OS — nothing else. No queries, no table or database names, no connection details, and nothing about the data you browse. Turning it off takes effect immediately.', - settings.telemetry, - toggleTelemetry, - )} - {/if} {#if show('Show query cards', 'Display the SQL the agent ran and the rows it returned')} {@render switchRow( 'Show query cards', @@ -672,6 +664,18 @@ {#if show($t('settings.mcpAutostart'), $t('settings.mcpAutostart.desc'))} {@render switchRow($t('settings.mcpAutostart'), $t('settings.mcpAutostart.desc'), settings.mcpAutoStart, toggleMcpAutoStart)} {/if} + + + {@render secLabel('Privacy')} + {#if show('Anonymous usage data', 'Help decide what to build next')} + {@render switchRow( + 'Anonymous usage data', + 'Sends which features you use, how often, the app version and your OS — nothing else. No queries, no table or database names, no connection details, and nothing about the data you browse. Turning it off takes effect immediately.', + settings.telemetry, + toggleTelemetry, + )} + {/if} {/snippet} {#snippet appearanceContent()} From 4c170d8111769e981503f2740c2d97eb311e29a3 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 9 Aug 2026 11:01:52 +0545 Subject: [PATCH 03/49] feat(shortcuts): Cmd+Shift+D disconnects, Enter confirms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disconnect was on Cmd+Alt+D — a three-finger stretch for something reached often, while Cmd+Shift+D went to the data view. The two swap: disconnect takes the easier chord, the data view takes Alt. Both are rebound in the SQL and ORM editors too, so the key means the same thing wherever focus is, and the shortcuts reference is updated to match. Enter now presses the confirm button, which needs nothing to be focused when the dialog opens — otherwise focus lands on Cancel and Enter would click it. Both are behind a confirmOnEnter prop, off by default and set only on the disconnect prompt: reconnecting is one click, whereas arming Enter on the shared prompt would put DROP TABLE one stray Return away. The confirm button shows a ↵ so the shortcut is discoverable. --- src/lib/components/ConfirmDialog.svelte | 51 +++++++++++++++++++ src/lib/components/DisconnectDialog.svelte | 1 + .../components/KeyboardShortcutsDialog.svelte | 4 +- src/lib/components/OrmRunner.svelte | 4 +- src/lib/components/SqlConsole.svelte | 2 + src/lib/components/SqlEditor.svelte | 4 +- src/lib/components/StudioShell.svelte | 15 ++++-- 7 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/lib/components/ConfirmDialog.svelte b/src/lib/components/ConfirmDialog.svelte index e488d85c..68d4af7a 100644 --- a/src/lib/components/ConfirmDialog.svelte +++ b/src/lib/components/ConfirmDialog.svelte @@ -27,6 +27,12 @@ /** 'destructive' paints the confirm button red. */ variant = /** @type {'default' | 'destructive'} */ ('default'), disabled = false, + /** + * Enter presses the confirm button, and nothing is focused when the dialog + * opens so that it can. Opt in only where confirming by accident is cheap to + * undo - it is off by default so a stray Return can never drop a table. + */ + confirmOnEnter = false, onconfirm = () => {}, oncancel = () => {}, /** Optional rich body, rendered in place of `description`. */ @@ -45,13 +51,54 @@ open = false oncancel() } + + /** @type {HTMLElement|null} */ + let contentEl = $state(null) + + /** + * Hold focus on the dialog itself instead of letting it land on Cancel, which + * is the first focusable child. With Cancel focused, Enter would click it - + * the exact opposite of the confirm-on-Enter below. Tab still reaches both + * buttons, and Escape still cancels. + * @param {Event} e + */ + function onOpenAutoFocus(e) { + if (!confirmOnEnter) return + e.preventDefault() + try { contentEl?.focus({ preventScroll: true }) } catch { /* not focusable */ } + } + + /** + * Enter confirms - the default-button behaviour every native alert dialog has, + * so a prompt can be answered without reaching for the mouse. + * + * Skipped wherever Enter already means something else: on a focused button the + * browser clicks it (confirming here too would fire Cancel *and* confirm), and + * in a textarea or contenteditable it inserts a newline. A plain input is left + * alone deliberately - Enter submitting a one-line field is what people expect. + * @param {KeyboardEvent} e + */ + function onkeydown(e) { + if (!confirmOnEnter) return + if (e.key !== 'Enter' || e.isComposing) return + if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return + const el = e.target + if (el instanceof HTMLElement && (el.closest('button, a[href], select, textarea') || el.isContentEditable)) return + if (disabled) return + e.preventDefault() + confirm() + }
@@ -100,6 +147,10 @@ > {#if confirmIcon}{/if} {confirmLabel} + + {#if confirmOnEnter} + ↵ + {/if}
diff --git a/src/lib/components/DisconnectDialog.svelte b/src/lib/components/DisconnectDialog.svelte index 1e3810cc..09485646 100644 --- a/src/lib/components/DisconnectDialog.svelte +++ b/src/lib/components/DisconnectDialog.svelte @@ -20,5 +20,6 @@ confirmLabel="Disconnect" confirmIcon="unplug" variant="destructive" + confirmOnEnter onconfirm={ondisconnect} /> diff --git a/src/lib/components/KeyboardShortcutsDialog.svelte b/src/lib/components/KeyboardShortcutsDialog.svelte index ace73361..d1b586f7 100644 --- a/src/lib/components/KeyboardShortcutsDialog.svelte +++ b/src/lib/components/KeyboardShortcutsDialog.svelte @@ -46,7 +46,7 @@ { keys: [mod, "⇧", "F"], desc: "Focus table filter" }, { keys: [mod, "D"], desc: "Switch database" }, { keys: [mod, "⇧", "C"], desc: "Switch connection" }, - { keys: [mod, opt, "D"], desc: "Disconnect" }, + { keys: [mod, opt, "D"], desc: "Data view" }, { keys: [mod, "/"], desc: "Keyboard shortcuts" }, { keys: ["F11"], desc: "Toggle fullscreen" }, ], @@ -55,7 +55,7 @@ label: "Views", icon: Monitor, shortcuts: [ - { keys: [mod, "⇧", "D"], desc: "Data view" }, + { keys: [mod, "⇧", "D"], desc: "Disconnect" }, { keys: [mod, "⇧", "S"], desc: "SQL editor" }, { keys: [mod, "⇧", "O"], desc: "ORM Runner" }, { keys: [mod, "⇧", "X"], desc: "Extensions" }, diff --git a/src/lib/components/OrmRunner.svelte b/src/lib/components/OrmRunner.svelte index 22277300..c78ce25d 100644 --- a/src/lib/components/OrmRunner.svelte +++ b/src/lib/components/OrmRunner.svelte @@ -54,6 +54,7 @@ onmodt = undefined, onmodshifte = undefined, onmodshiftd = undefined, + onmodaltd = undefined, } = $props(); /** @type {HTMLElement | null} */ @@ -120,7 +121,7 @@ /** @param {monaco.editor.IStandaloneCodeEditor} ed */ function registerShortcuts(ed) { - const { CtrlCmd, Shift } = monaco.KeyMod + const { CtrlCmd, Shift, Alt } = monaco.KeyMod const { Enter, KeyS, KeyI, KeyB, KeyW, KeyN, KeyM, KeyT, KeyD, KeyE } = monaco.KeyCode const run = (/** @type {(() => void) | undefined} */ fn) => fn?.() @@ -136,6 +137,7 @@ ed.addCommand(CtrlCmd | KeyM, () => run(onmodm)) ed.addCommand(CtrlCmd | KeyT, () => run(onmodt)) ed.addCommand(CtrlCmd | Shift | KeyD, () => run(onmodshiftd)) + ed.addCommand(CtrlCmd | Alt | KeyD, () => run(onmodaltd)) ed.addCommand(CtrlCmd | Shift | KeyE, () => run(onmodshifte)) } diff --git a/src/lib/components/SqlConsole.svelte b/src/lib/components/SqlConsole.svelte index 56e0fc51..41a0e64c 100644 --- a/src/lib/components/SqlConsole.svelte +++ b/src/lib/components/SqlConsole.svelte @@ -83,6 +83,7 @@ onmodt = undefined, onmodshifte = undefined, onmodshiftd = undefined, + onmodaltd = undefined, onmodshifto = undefined, onmodshiftb = undefined, queryHistoryVisible = $bindable(false), @@ -745,6 +746,7 @@ {onmodt} {onmodshifte} {onmodshiftd} + {onmodaltd} {onmodshifto} onmodj={toggleOutput} onmodshiftb={() => { queryHistoryVisible = !queryHistoryVisible; onmodshiftb?.() }} diff --git a/src/lib/components/SqlEditor.svelte b/src/lib/components/SqlEditor.svelte index 47397d03..40b37435 100644 --- a/src/lib/components/SqlEditor.svelte +++ b/src/lib/components/SqlEditor.svelte @@ -40,6 +40,7 @@ onmodt = undefined, onmodshifte = undefined, onmodshiftd = undefined, + onmodaltd = undefined, onmodshifto = undefined, onmodj = undefined, onmodshiftb = undefined, @@ -119,7 +120,7 @@ /** @param {monaco.editor.IStandaloneCodeEditor} ed */ function registerAppShortcuts(ed) { - const { CtrlCmd, Shift } = monaco.KeyMod + const { CtrlCmd, Shift, Alt } = monaco.KeyMod const { KeyK, KeyL, KeyR, KeyS, KeyI, KeyB, KeyW, KeyN, KeyM, KeyT, KeyD, KeyO, KeyE, KeyJ, Enter } = monaco.KeyCode /** @param {() => void | undefined} fn */ @@ -185,6 +186,7 @@ ed.addCommand(CtrlCmd | KeyM, () => run(onmodm)) ed.addCommand(CtrlCmd | KeyT, () => run(onmodt)) ed.addCommand(CtrlCmd | Shift | KeyD, () => run(onmodshiftd)) + ed.addCommand(CtrlCmd | Alt | KeyD, () => run(onmodaltd)) ed.addCommand(CtrlCmd | Shift | KeyE, () => run(onmodshifte)) ed.addCommand(CtrlCmd | Shift | KeyO, () => run(onmodshifto)) ed.addCommand(CtrlCmd | KeyJ, () => run(onmodj)) diff --git a/src/lib/components/StudioShell.svelte b/src/lib/components/StudioShell.svelte index 2acff9c2..1823b96b 100644 --- a/src/lib/components/StudioShell.svelte +++ b/src/lib/components/StudioShell.svelte @@ -2011,11 +2011,13 @@ let rowSearch = $state('') commandOpen = true }) - // Disconnect the current connection (opens the confirm dialog). + // Data view (⌘⌥D). It used to be ⌘⇧D; that key now disconnects, which is the + // more consequential of the two and the one worth having under the easier + // chord. createHotkey('Mod+Alt+D', (e) => { if (!connection) return e.preventDefault() - showDisconnectDialog = true + void focusDataView() }) // Open the keyboard-shortcuts reference (Ctrl/⌘+/ - same key as Mod+? without shift). @@ -2067,10 +2069,11 @@ let rowSearch = $state('') })() }) + // Disconnect (⌘⇧D) - opens the confirm dialog, where Enter confirms. createHotkey('Mod+Shift+D', (e) => { if (!connection) return e.preventDefault() - void focusDataView() + showDisconnectDialog = true }) createHotkey('Mod+Shift+S', (e) => { @@ -6281,7 +6284,8 @@ let rowSearch = $state('') onmodm={() => cycleTheme()} onmodt={() => { if (connection) { commandPage = 'tables'; commandOpen = true } }} onmodshifte={() => { if (connection) aiMode ? exitAiMode() : enterAiMode() }} - onmodshiftd={() => { if (connection) void focusDataView() }} + onmodshiftd={() => { if (connection) showDisconnectDialog = true }} + onmodaltd={() => { if (connection) void focusDataView() }} /> {/await} @@ -6322,7 +6326,8 @@ let rowSearch = $state('') onmodm={() => cycleTheme()} onmodt={() => { if (connection) { commandPage = 'tables'; commandOpen = true } }} onmodshifte={() => { if (connection) aiMode ? exitAiMode() : enterAiMode() }} - onmodshiftd={() => { if (connection) void focusDataView() }} + onmodshiftd={() => { if (connection) showDisconnectDialog = true }} + onmodaltd={() => { if (connection) void focusDataView() }} onmodshifto={() => { if (connection) openOrmTab() }} onqueryrefresh={refreshQueryStores} onhistoryselect={(sql) => void openQueryInEditor(sql)} From 5c2db8b2e7439b8efa808a8f71127ebb8a1776e8 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 9 Aug 2026 11:06:16 +0545 Subject: [PATCH 04/49] feat(settings): default the agent chat and code fonts to 16px MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both inherited the app's 14/13px UI scale, which is the right size for dense chrome and the wrong one for prose and code you sit and read. Existing installs have the old value written into localStorage — not because anyone chose it, but because saving any unrelated setting persists the whole settings object. A stored size that still equals the old default is treated as unset and moves up with it; anything else is a real choice and is left alone. --- src/lib/settings-consistency.test.js | 14 +++++++++++++ src/lib/stores/settings.js | 31 ++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/lib/settings-consistency.test.js b/src/lib/settings-consistency.test.js index 5318ca7e..bb6a4e23 100644 --- a/src/lib/settings-consistency.test.js +++ b/src/lib/settings-consistency.test.js @@ -91,4 +91,18 @@ describe('settings option lists stay in step with their consumers', () => { const offenders = files.filter((f) => /\.download\s*=/.test(read(f))) expect(offenders).toEqual([]) }) + + it('defaults the agent fonts to a size the picker actually offers', () => { + // loadSettings only keeps a stored value that is in AGENT_FONT_SIZES, and + // the picker is built from the same list. A default outside it would show + // an empty select and silently snap back on the next load. + const sizes = listBlock(settings, 'AGENT_FONT_SIZES') + .match(/\d+/g) + .map(Number) + for (const name of ['DEFAULT_AGENT_CHAT_FONT', 'DEFAULT_AGENT_CODE_FONT', 'LEGACY_AGENT_CHAT_FONT', 'LEGACY_AGENT_CODE_FONT']) { + const m = settings.match(new RegExp(`${name} = (\\d+)`)) + expect(m, `${name} not found — was it renamed?`).toBeTruthy() + expect(sizes, `${name} = ${m[1]} is not offered by the picker`).toContain(Number(m[1])) + } + }) }) diff --git a/src/lib/stores/settings.js b/src/lib/stores/settings.js index 92180168..94b0cd81 100644 --- a/src/lib/stores/settings.js +++ b/src/lib/stores/settings.js @@ -201,8 +201,31 @@ export const DEFAULT_NULL_SORT = 'unset' /** Selectable font sizes (px) for the AI/agent chat + code blocks. */ export const AGENT_FONT_SIZES = /** @type {const} */ ([12, 13, 14, 15, 16]) -export const DEFAULT_AGENT_CHAT_FONT = 14 -export const DEFAULT_AGENT_CODE_FONT = 13 +export const DEFAULT_AGENT_CHAT_FONT = 16 +export const DEFAULT_AGENT_CODE_FONT = 16 + +/** + * Chat and code both used to default to the app's 14/13px UI scale, which is the + * right size for dense chrome and the wrong one for prose you actually read. The + * defaults below moved to 16px; these are what they used to be, so a stored value + * that still matches can be recognised as "never chosen" and moved up with them. + * @see migrateAgentFont + */ +const LEGACY_AGENT_CHAT_FONT = 14 +const LEGACY_AGENT_CODE_FONT = 13 + +/** + * Existing installs have the old default written into localStorage - not because + * anyone picked it, but because saving any unrelated setting persists the whole + * object. Left alone they would keep 14/13 forever and never see the new default. + * A value that still equals the old default is treated as unset; anything else is + * a real choice and is left exactly as it is. + * @param {unknown} stored @param {number} legacy @param {number} next + */ +function migrateAgentFont(stored, legacy, next) { + if (!AGENT_FONT_SIZES.includes(/** @type {never} */ (stored))) return next + return stored === legacy ? next : /** @type {number} */ (stored) +} /** Thinking-indicator visual styles for the agent chat. */ export const THINKING_STYLES = /** @type {const} */ ([ { id: 'shimmer', label: 'Shimmer' }, @@ -438,8 +461,8 @@ export function loadSettings() { const cmdkAiEnabled = parsed.cmdkAiEnabled === true const liveModeEnabled = parsed.liveModeEnabled === true const nullSortOrder = NULL_SORT_IDS.includes(parsed.nullSortOrder) ? parsed.nullSortOrder : DEFAULT_NULL_SORT - const agentChatFontSize = AGENT_FONT_SIZES.includes(parsed.agentChatFontSize) ? parsed.agentChatFontSize : DEFAULT_AGENT_CHAT_FONT - const agentCodeFontSize = AGENT_FONT_SIZES.includes(parsed.agentCodeFontSize) ? parsed.agentCodeFontSize : DEFAULT_AGENT_CODE_FONT + const agentChatFontSize = migrateAgentFont(parsed.agentChatFontSize, LEGACY_AGENT_CHAT_FONT, DEFAULT_AGENT_CHAT_FONT) + const agentCodeFontSize = migrateAgentFont(parsed.agentCodeFontSize, LEGACY_AGENT_CODE_FONT, DEFAULT_AGENT_CODE_FONT) const agentThinkingStyle = THINKING_STYLE_IDS.includes(parsed.agentThinkingStyle) ? parsed.agentThinkingStyle : DEFAULT_THINKING_STYLE const agentShowQueryCards = parsed.agentShowQueryCards !== false const agentWebAccess = parsed.agentWebAccess === true From 6893bd085f17918a3b830ec4995bfcb6039a54e9 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 9 Aug 2026 11:10:28 +0545 Subject: [PATCH 05/49] refactor(providers): one sign-in card instead of four shapes per provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloudflare and the OAuth providers each drew their own idle CTA, waiting card, loading row and error card — eight hand-rolled layouts, no two the same height, so every step of a sign-in resized the pane under the cursor. They now share ProviderAuthPanel: one frame, four tones. The mark, the two lines of copy and the footer hold their positions from the first frame to the last; only the contents change, and the progress track is reserved whether or not it is running so it cannot nudge the footer down. The idle step was a full-bleed black slab with an arrow — a landing-page CTA, not a control in a desktop app. It is now the shared -

Opens your browser to authorize · same PKCE flow as Wrangler

-
+ + {#snippet mark()}{/snippet} + {#snippet action()} + + {/snippet} + {:else if phase === 'authorizing'} - -
-
-
- - -
-
-

Waiting for Cloudflare…

-

- Finish authorizing in the browser tab, then come back here. -

-
-
-
- -
-
+ + {#snippet mark()}{/snippet} + {:else if phase === 'fetching'} - -
- - Loading your Cloudflare accounts… -
+ + {#snippet mark()}{/snippet} + + + {:else if phase === 'error'} + + {#snippet mark()}{/snippet} + {#snippet action()} + + {/snippet} + {:else if phase === 'selecting'} @@ -337,56 +346,7 @@
{/if} - {:else if phase === 'error'} -
-
-
- -
-
-

{shownError.title}

-

{shownError.detail}

-
-
-
- -
-
{/if} - diff --git a/src/lib/components/ConnectionModal.svelte b/src/lib/components/ConnectionModal.svelte index 91c28094..73335744 100644 --- a/src/lib/components/ConnectionModal.svelte +++ b/src/lib/components/ConnectionModal.svelte @@ -3129,7 +3129,10 @@ {:else if isProvider} -
+ +
@@ -3166,7 +3169,7 @@ {@render advancedSection()}
{:else if dbType === "d1"} -
+
{#key dbType} + /** + * The frame the provider sign-in flows live in. + * + * Cloudflare and the OAuth providers each used to draw their own idle CTA, + * waiting card, loading row and error card. Four shapes per provider, none of + * them the same size, so every step of a sign-in resized the pane under the + * cursor - and the idle step was a full-bleed black slab that read like a + * landing-page CTA rather than a control in a desktop app. + * + * This is one card with four tones. The mark, the two lines of copy and the + * footer stay exactly where they are from the first frame to the last; only + * their contents change. Nothing moves while you sign in. + */ + import Loader2 from '@lucide/svelte/icons/loader-2' + import { cn } from '$lib/utils.js' + + let { + /** 'idle' waits on the user · 'busy' waits on the network · 'error' failed. */ + tone = /** @type {'idle' | 'busy' | 'error'} */ ('idle'), + title = '', + subtitle = '', + /** Quiet line in the footer, left of the action. */ + hint = '', + /** Indeterminate bar under the header - proof a long wait is still alive. */ + progress = false, + /** The provider logo. Sits in a fixed 36px well so every provider aligns. */ + mark = undefined, + /** The one button for this step. */ + action = undefined, + } = $props() + + +
+
+
+ {#if tone === 'busy'} + + + {/if} + {#if mark}{@render mark()}{:else}{/if} +
+
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+
+ + +
+ {#if progress} + + {/if} +
+ +
+ {hint} + {#if action}
{@render action()}
{/if} +
+
+ + diff --git a/src/lib/components/ProviderConnect.svelte b/src/lib/components/ProviderConnect.svelte index fa2be71e..0dc47136 100644 --- a/src/lib/components/ProviderConnect.svelte +++ b/src/lib/components/ProviderConnect.svelte @@ -11,6 +11,8 @@ import ArrowRight from '@lucide/svelte/icons/arrow-right' import X from '@lucide/svelte/icons/x' import DbIcon from './DbIcon.svelte' + import ProviderAuthPanel from './ProviderAuthPanel.svelte' + import { Button } from '$lib/components/ui/button/index.js' import Search from '@lucide/svelte/icons/search' import KeyRound from '@lucide/svelte/icons/key-round' import Eye from '@lucide/svelte/icons/eye' @@ -252,77 +254,81 @@
{#if phase === 'idle'} - {#if meta?.mode === 'token'} -
- { if (e.key === 'Enter') saveToken() }} - /> - -

Paste your {meta?.name} connection string to continue.

-
+ + + {#snippet mark()}{/snippet} + {#snippet action()} + + {/snippet} + + { if (e.key === 'Enter') saveToken() }} + /> {:else} -
- -

Opens your browser to authorize · secure PKCE flow

-
+ + {#snippet mark()}{/snippet} + {#snippet action()} + + {/snippet} + {/if} {:else if phase === 'authorizing'} -
-
- -
- - -
-
-

Waiting for {meta?.name}…

-

- Finish authorizing in the browser tab, then come back here. -

-
-
- -
- -
-
- Times out in 5 min - -
-
+ + {#snippet mark()}{/snippet} + {#snippet action()} + + {/snippet} + {:else if phase === 'fetching'} -
- Loading your databases… -
+ + {#snippet mark()}{/snippet} + + + {:else if phase === 'error'} + + {#snippet mark()}{/snippet} + {#snippet action()} + + {/snippet} + {:else if phase === 'selecting' || phase === 'building' || phase === 'password'} @@ -449,55 +455,6 @@
{/if} - {:else if phase === 'error'} -
-
-
- -
-
-

{shownError.title}

-

{shownError.detail}

-
-
-
- -
-
{/if}
- From 410ba014c28989e61dd4de0ee8bc7474d3e0f4a5 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 9 Aug 2026 11:19:52 +0545 Subject: [PATCH 06/49] fix(providers): make the sign-in a section, not a floating card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bordered box hovering above the form it belongs to reads as an advert rather than as the first step of that form, and capping it at 520px made it worse: the Advanced grid inside is six columns, so the read-only label got a 173px cell and wrapped onto three lines. Now it is a plain section — icon, copy, one action, a rule — full width and flush with the fields under it. The rule doubles as the progress track, so a long wait shows life without a bar appearing from nowhere and pushing the rest of the pane down, and the copy is capped at a readable measure instead of running the full width of the pane. --- src/lib/components/ConnectionModal.svelte | 7 +-- src/lib/components/ProviderAuthPanel.svelte | 62 +++++++++++---------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/lib/components/ConnectionModal.svelte b/src/lib/components/ConnectionModal.svelte index 73335744..91c28094 100644 --- a/src/lib/components/ConnectionModal.svelte +++ b/src/lib/components/ConnectionModal.svelte @@ -3129,10 +3129,7 @@
{:else if isProvider} - -
+
@@ -3169,7 +3166,7 @@ {@render advancedSection()}
{:else if dbType === "d1"} -
+
{#key dbType} -
-
+
+
{#if tone === 'busy'} @@ -50,27 +47,32 @@ {/if} {#if mark}{@render mark()}{:else}{/if}
-
+ +

{title}

{#if subtitle} -

{subtitle}

+ +

+ {subtitle} +

+ {/if} + {#if hint} +

{hint}

{/if}
-
- -
- {#if progress} - + {#if action} +
{@render action()}
{/if}
-
- {hint} - {#if action}
{@render action()}
{/if} +
+ {#if progress} + + {/if}
-
+