From 54b755f1ceea27af5a417e3f2bc9e2571cf21e8f Mon Sep 17 00:00:00 2001 From: broisnischal Date: Thu, 4 Jun 2026 21:36:38 +0545 Subject: [PATCH 1/8] feat::revamp redesign --- package-lock.json | 17 ++------ src-tauri/Cargo.lock | 24 ++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/lib.rs | 14 +++++++ src/lib/components/DataTable.svelte | 61 +++++++++++++++++------------ src/lib/stores/settings.js | 31 ++++++++++++--- 6 files changed, 104 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index d76de961..e2ee83c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "db-studio", - "version": "0.3.1", + "version": "0.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "db-studio", - "version": "0.3.1", + "version": "0.3.3", "dependencies": { "@dagrejs/dagre": "^3.0.0", "@fontsource-variable/geist": "^5.2.9", @@ -97,7 +97,6 @@ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" @@ -109,7 +108,6 @@ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -205,7 +203,6 @@ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz", "integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/helpers": "^0.5.0" } @@ -1652,7 +1649,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1832,7 +1828,6 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.4.tgz", "integrity": "sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -2242,7 +2237,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3343,7 +3337,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3652,7 +3645,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz", "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3742,7 +3734,6 @@ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -3771,8 +3762,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", @@ -3960,7 +3950,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 61092aee..4fdca7d1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -97,6 +97,7 @@ dependencies = [ "objc2", "objc2-app-kit", "objc2-quartz-core", + "objc2-web-kit", "reqwest 0.12.28", "rust_decimal", "serde", @@ -2924,6 +2925,16 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2", + "objc2-core-foundation", +] + [[package]] name = "objc2-metal" version = "0.3.2" @@ -2964,6 +2975,17 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", +] + [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -3007,6 +3029,8 @@ dependencies = [ "objc2-app-kit", "objc2-core-foundation", "objc2-foundation", + "objc2-javascript-core", + "objc2-security", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 98a0597c..c0f2c560 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -48,6 +48,7 @@ getrandom = "0.2" objc2 = "0.6" objc2-app-kit = { version = "0.3", features = ["NSWindow", "NSView", "objc2-quartz-core"] } objc2-quartz-core = { version = "0.3", features = ["CALayer"] } +objc2-web-kit = { version = "0.3", features = ["WKWebView"] } [profile.release] strip = true diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 52747208..1c73a7fb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -70,6 +70,10 @@ pub fn run() { // keep disabled in release. The toggle_devtools command also exposes // them on demand via F12. .devtools(cfg!(debug_assertions)) + // The app implements its own canvas-table zoom; disable Tauri's injected + // Ctrl/Cmd+wheel webview polyfill (macOS/Linux) so pinch near column + // resize handles doesn't magnify the whole webview. + .zoom_hotkeys_enabled(false) .on_navigation(|url| { let scheme = url.scheme(); if matches!(scheme, "tauri" | "ipc") { @@ -101,6 +105,16 @@ pub fn run() { } } } + + // Disable WKWebView trackpad pinch magnification — the canvas table + // has its own zoom and pinch during column resize was blowing up the view. + let _ = window.with_webview(|webview| unsafe { + use objc2_web_kit::WKWebView; + let view: &WKWebView = &*webview.inner().cast(); + view.setAllowsMagnification(false); + view.setMagnification(1.0); + view.setPageZoom(1.0); + }); } if cfg!(debug_assertions) { diff --git a/src/lib/components/DataTable.svelte b/src/lib/components/DataTable.svelte index e6cfe398..4b6a6e30 100644 --- a/src/lib/components/DataTable.svelte +++ b/src/lib/components/DataTable.svelte @@ -2301,40 +2301,48 @@ } } - // Master effect — sizes the canvas backing store (DPR-aware) and repaints. - // Doing both here, and never reading+writing the same signal, keeps it a pure - // sink: it tracks every input that affects the grid and writes no reactive - // state, so it can't loop. - $effect(() => { - void rows; void columns; void columnWidths - void pinnedColumns; void hiddenColumns; void selected; void focusedRow - void focusedCol; void editingCell; void pendingEdits; void expandedRows - void rowSort; void _scrollTop; void _scrollLeft; void _viewportWidth - void _viewportHeight; void hoveredRow; void hoveredColName; void _resizeHoverCol - void resizingColName; void newRowDrafts; void insertSaving; void colMeta - void geom; void rowTops; void _redrawToken; void foreignKeys; void indexes - void _colCache; void expandedRowHeights; void fkSubview; void virtualRelCols; void VIRTUAL_COL_W - // Read the zoom store directly so this effect re-runs the moment any tab changes zoom. - void zoomState.value; void canvasZoom - + /** Size the canvas backing store (DPR-aware). Always sync CSS px size so macOS + * WebKit can't display a stale CSS box over a mismatched bitmap (blurry zoom). */ + function syncCanvasSurface() { const canvas = canvasEl const probe = colorProbe - if (!canvas || !probe) return + if (!canvas || !probe) return false if (!_readColor) _readColor = createColorReader(probe) + const cssW = Math.max(1, Math.round(_viewportWidth)) + const cssH = Math.max(1, Math.round(_viewportHeight)) const dpr = Math.min(window.devicePixelRatio || 1, 2) - const bw = Math.max(1, Math.round(_viewportWidth * dpr)) - const bh = Math.max(1, Math.round(_viewportHeight * dpr)) + const bw = Math.max(1, Math.round(cssW * dpr)) + const bh = Math.max(1, Math.round(cssH * dpr)) + canvas.style.width = cssW + 'px' + canvas.style.height = cssH + 'px' // Only touch canvas.width/height when it actually changes — assigning clears // the canvas, and we want to avoid a redundant clear on every repaint. if (canvas.width !== bw || canvas.height !== bh) { canvas.width = bw canvas.height = bh - canvas.style.width = _viewportWidth + 'px' - canvas.style.height = _viewportHeight + 'px' } _ctx = canvas.getContext('2d') if (_ctx) _ctx.setTransform(dpr, 0, 0, dpr, 0, 0) - draw() + return !!_ctx + } + + // Layout / sizing effect — resize the backing store when geometry or viewport changes. + $effect(() => { + void rows; void columns; void columnWidths + void pinnedColumns; void hiddenColumns; void selected; void focusedRow + void focusedCol; void editingCell; void pendingEdits; void expandedRows + void rowSort; void _scrollTop; void _scrollLeft; void _viewportWidth + void _viewportHeight; void newRowDrafts; void insertSaving; void colMeta + void geom; void rowTops; void _redrawToken; void foreignKeys; void indexes + void _colCache; void expandedRowHeights; void fkSubview; void virtualRelCols; void VIRTUAL_COL_W + void zoomState.value; void canvasZoom + if (syncCanvasSurface()) draw() + }) + + // Hover / affordance repaint — pointer moves must not re-touch canvas dimensions. + $effect(() => { + void hoveredRow; void hoveredColName; void _resizeHoverCol; void resizingColName + if (_ctx) draw() }) // ── Canvas pointer interaction ────────────────────────────────────────── @@ -2575,17 +2583,21 @@ // Ctrl/Cmd+Scroll → zoom the canvas. Must use { passive: false } so preventDefault // works — Svelte's onwheel directive registers a passive listener by default which // can't call preventDefault, causing the browser to intercept the event first. + // Capture phase so we run after the window-level webview guard (preventDefault only) + // but before the browser applies native magnification. $effect(() => { const el = tableContainer if (!el) return function onWheel(/** @type {WheelEvent} */ e) { if (!e.ctrlKey && !e.metaKey) return + // Trackpad pinch / scroll while grabbing a column edge is not zoom intent. + if (resizingColName || _resizeHoverCol) return e.preventDefault() e.stopPropagation() adjustZoom(e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP) } - el.addEventListener('wheel', onWheel, { passive: false }) - return () => el.removeEventListener('wheel', onWheel) + el.addEventListener('wheel', onWheel, { capture: true, passive: false }) + return () => el.removeEventListener('wheel', onWheel, { capture: true }) }) function onCanvasContextMenu(/** @type {MouseEvent} */ e, /** @type {((e: MouseEvent) => void) | undefined} */ bitsOpen) { @@ -2652,6 +2664,7 @@
} patch */ From 43b2b90a24365a9d7d7546a18922e63f1df9d04c Mon Sep 17 00:00:00 2001 From: broisnischal Date: Fri, 5 Jun 2026 08:21:17 +0545 Subject: [PATCH 2/8] feat::fixing canvas table zooming --- docs/canvas-zoom-resize-fix.md | 119 ++++++++ src-tauri/src/lib.rs | 13 +- src/lib/canvas-table.js | 14 + src/lib/components/DataTable.svelte | 367 ++++++++++++++++--------- src/lib/components/StudioShell.svelte | 2 +- src/lib/components/TitleBar.svelte | 13 +- src/lib/components/UpdateDialog.svelte | 2 +- src/lib/stores/canvas-zoom.svelte.js | 34 +-- src/lib/stores/settings.js | 50 +++- src/main.js | 44 ++- 10 files changed, 481 insertions(+), 177 deletions(-) create mode 100644 docs/canvas-zoom-resize-fix.md diff --git a/docs/canvas-zoom-resize-fix.md b/docs/canvas-zoom-resize-fix.md new file mode 100644 index 00000000..06165e91 --- /dev/null +++ b/docs/canvas-zoom-resize-fix.md @@ -0,0 +1,119 @@ +# Fix: Canvas table magnifies/blurs on macOS resize-handle hover + +**Status:** Resolved +**Platform:** macOS (WKWebView) — Windows/Linux unaffected +**Area:** `DataTable.svelte` canvas grid, app zoom, Tauri webview + +## Symptom + +Hovering or dragging a column resize handle made the **table alone** look huge +and blurry, while the sidebar and the rest of the UI stayed at normal size. + +The mismatch was the key tell: + +- The sidebar/UI scale via CSS (`--app-font-size`, derived from `--app-zoom`). +- The table is a ``. When it ballooned independently and looked + **blurry** (not crisp), a bitmap was being scaled by a layer *above* the app, + not the app's own renderer. + +## Root cause + +It was **WebKit page-zoom**, not the app's canvas zoom and not pinch +magnification. The diagnostic (`window.__zoomDiag()` while the table looked +wrong) confirmed it: + +- `visualViewport.scale` stayed at **1** (so not pinch magnification) +- `devicePixelRatio` **jumped** (the signature of page-zoom) +- macOS Accessibility → Zoom was **off** (so not a system setting) + +Two contributing paths fed it: + +1. **Tauri's injected zoom polyfill.** When `zoom_hotkeys_enabled` is on, Tauri + injects a script that listens on the **legacy `mousewheel` event** (not the + standard `wheel` event) and calls `set_webview_zoom` whenever the scroll has + `ctrlKey` set. On macOS a trackpad **pinch arrives as `ctrl`+wheel**, so a + stray finger movement near a resize handle page-zoomed the whole webview. + Earlier fixes only blocked `wheel`, which this polyfill never uses. + +2. **A separate, persisted canvas zoom.** `db-studio:canvas-zoom` in + `localStorage` could drift independently of the app's `settings.zoom`, so the + grid could stay "zoomed" across sessions while the sidebar did not. + +Because the resize hit-test lived **on the canvas header**, the pinch landed on +the canvas and triggered page-zoom before any JS guard could intervene. + +## The fix + +### 1. Column resize moved off the canvas to a DOM overlay +`src/lib/components/DataTable.svelte` + +- Resize handles are now absolutely-positioned `
`s (a sticky overlay at + `z-index: 2`) sitting on each column's right edge, derived from `geom`. +- The canvas no longer hit-tests or handles header resize pointer events. +- Each handle has `pointer-events: auto`, `touch-none`, shows `col-resize`, and + swallows `wheel` / `mousewheel` / `gesture*` while resetting webview zoom. + +### 2. Wheel/pinch zoom disabled app-wide — zoom is keyboard-only +`src/lib/stores/settings.js` + +- Global **capture-phase** listeners on `wheel`, `mousewheel`, and the WebKit + `gesturestart`/`gesturechange`/`gestureend` events call + `preventDefault()` + `stopImmediatePropagation()` (beats Tauri's polyfill), + then `resetWebviewZoom()`. +- Zoom is now driven **only** by `Cmd/Ctrl + = / - / 0`. +- `resetWebviewZoom()` snaps the native page-zoom back to `1` via + `getCurrentWebview().setZoom(1)`. + +### 3. App zoom is the single source of truth +`src/lib/stores/canvas-zoom.svelte.js`, `src/lib/stores/settings.js` + +- Removed the independent `db-studio:canvas-zoom` persistence. `zoomState` is + now a plain mirror of `settings.zoom`; `applySettings()` is the only writer. +- `applySettings()` clears the legacy `db-studio:canvas-zoom` key and calls + `resetWebviewZoom()` on every apply. + +### 4. devicePixelRatio watchdog +`src/main.js` + +- On launch and on every `visualViewport`/`window` resize, if + `visualViewport.scale !== 1` **or** `devicePixelRatio` drifts from baseline, + call `resetWebviewZoom()` and re-baseline (so a genuine monitor/DPI change + isn't fought). `window.__zoomDiag()` remains as a console probe. + +### 5. Native webview zoom disabled at the source +`src-tauri/src/lib.rs`, `src-tauri/Cargo.toml` + +- `.zoom_hotkeys_enabled(false)` — stops Tauri from injecting the `mousewheel` + zoom polyfill at all. +- macOS `with_webview`: `setAllowsMagnification(false)`, `setMagnification(1.0)`, + `setPageZoom(1.0)` via `objc2-web-kit`. + +## Files touched + +- `src/lib/components/DataTable.svelte` — DOM resize handles, canvas no longer + resizes columns, dpr re-sync of the backing store. +- `src/lib/stores/settings.js` — keyboard-only zoom, global wheel/pinch blockers, + `resetWebviewZoom()`. +- `src/lib/stores/canvas-zoom.svelte.js` — `zoomState` mirrors app zoom only. +- `src/main.js` — launch reset + dpr watchdog + `__zoomDiag()`. +- `src-tauri/src/lib.rs` — `zoom_hotkeys_enabled(false)`, WKWebView zoom off. +- `src-tauri/Cargo.toml` — `objc2-web-kit` dependency. + +## How to verify + +A **full restart** is required (`npm run tauri`) because Rust changes don't hot-reload. + +1. If the table is still large from a previous session, press **Cmd+0**. +2. Hover/drag a column resize edge — cursor shows `col-resize`, table size does + **not** change, no blur. +3. Pinch near a resize handle — nothing balloons. +4. **Cmd + =** / **Cmd + -** zoom the **whole app** (sidebar + table together). +5. Optional: run `__zoomDiag()` in DevTools — `devicePixelRatio` should stay put. + +## Notes + +- Tauri page-zoom listens on `mousewheel`, **not** `wheel`. Blocking only `wheel` + is insufficient on macOS. +- Page-zoom raises `devicePixelRatio` with `visualViewport.scale === 1`; pinch + magnification raises `visualViewport.scale`. They are distinct and need + different handling. diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1c73a7fb..f81e9e8a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -70,9 +70,11 @@ pub fn run() { // keep disabled in release. The toggle_devtools command also exposes // them on demand via F12. .devtools(cfg!(debug_assertions)) - // The app implements its own canvas-table zoom; disable Tauri's injected - // Ctrl/Cmd+wheel webview polyfill (macOS/Linux) so pinch near column - // resize handles doesn't magnify the whole webview. + // The app implements its own CSS-based zoom; disable Tauri's injected + // zoom polyfill. On macOS/Linux that polyfill attaches a `mousewheel` + // (legacy event) listener that calls set_webview_zoom on ctrl+scroll — + // a stray trackpad pinch near a column resize handle would then page-zoom + // the whole webview (devicePixelRatio jumps, canvas renders blurry). .zoom_hotkeys_enabled(false) .on_navigation(|url| { let scheme = url.scheme(); @@ -106,8 +108,9 @@ pub fn run() { } } - // Disable WKWebView trackpad pinch magnification — the canvas table - // has its own zoom and pinch during column resize was blowing up the view. + // Defensive: disable every native WKWebView zoom path. App zoom is + // CSS-based (--app-zoom); stray pinch near column resize handles + // must never page-zoom the webview (devicePixelRatio drift → blur). let _ = window.with_webview(|webview| unsafe { use objc2_web_kit::WKWebView; let view: &WKWebView = &*webview.inner().cast(); diff --git a/src/lib/canvas-table.js b/src/lib/canvas-table.js index f9c08054..b987c755 100644 --- a/src/lib/canvas-table.js +++ b/src/lib/canvas-table.js @@ -47,12 +47,26 @@ export function createColorReader(probe) { }; } +// Memoize results — (colour, alpha) combos are few and stable, but this runs in +// the per-frame draw loop, so caching avoids repeated regex parsing on scroll. +/** @type {Map} */ +const _alphaCache = new Map(); + /** * Apply an alpha to a CSS colour string regardless of notation (rgb/rgba/hsl, * functional space-separated, oklch/oklab/color(), or hex). Returns a string * the canvas understands. */ export function withAlpha(/** @type {string} */ color, /** @type {number} */ a) { + const key = color + "@" + a; + const cached = _alphaCache.get(key); + if (cached !== undefined) return cached; + const out = _computeAlpha(color, a); + _alphaCache.set(key, out); + return out; +} + +function _computeAlpha(/** @type {string} */ color, /** @type {number} */ a) { const c = color.trim(); const fnMatch = c.match(/^([a-z]+)\((.*)\)$/i); if (fnMatch) { diff --git a/src/lib/components/DataTable.svelte b/src/lib/components/DataTable.svelte index 4b6a6e30..dd6f5968 100644 --- a/src/lib/components/DataTable.svelte +++ b/src/lib/components/DataTable.svelte @@ -1,11 +1,9 @@ + +
+ + +
+ + Release Notes + v{version} +
+ + +
+
+ + +
+
+ +
+
+

What's New

+

Stroke v{version}

+
+
+ + + {#if grouped.length > 0} +
+ {#each grouped as group (group.type)} +
+ +
+ + + {group.meta.label} + +
+ + +
+ {#each group.sections as section (section.title)} +
+
+ {section.title} +
+
    + {#each section.items as item} +
  • + + + {@html renderInline(item)} +
  • + {/each} +
+
+ {/each} +
+
+ {/each} +
+ + {:else if releaseNotes?.trim()} + +
+
{releaseNotes}
+
+ + {:else} +
+

No release notes available for this version.

+

Check the GitHub releases page for details.

+
+ {/if} + +
+
+ + +
+ + {#if status === 'available'} +
+ + +
+ + {:else if status === 'downloading'} +
+
+ + + Downloading update… + + + {totalBytes > 0 ? `${fmt(downloadedBytes)} / ${fmt(totalBytes)}` : `${progress}%`} + +
+
+
+
+
+ + {:else if status === 'done'} + + + {:else if status === 'error'} +
+ +

{errorMsg || 'Update failed. Please try again.'}

+
+ {/if} + +
+
diff --git a/src/lib/components/SecuritySqlModal.svelte b/src/lib/components/SecuritySqlModal.svelte index 4dfe5d44..db171073 100644 --- a/src/lib/components/SecuritySqlModal.svelte +++ b/src/lib/components/SecuritySqlModal.svelte @@ -4,7 +4,7 @@ import { configureMonacoWorkers } from '$lib/monaco-env.js' import { registerMonacoSqlFormatter } from '$lib/format-sql.js' import { - defineDbStudioMonacoThemes, + defineStrokeMonacoThemes, monacoThemeId, readEditorFontOptions, } from '$lib/monaco-themes.js' @@ -32,7 +32,7 @@ onMount(() => { configureMonacoWorkers() - defineDbStudioMonacoThemes() + defineStrokeMonacoThemes() registerMonacoSqlFormatter(monaco) if (!container) return diff --git a/src/lib/components/SettingsDialog.svelte b/src/lib/components/SettingsDialog.svelte index 1a692b9f..d954826d 100644 --- a/src/lib/components/SettingsDialog.svelte +++ b/src/lib/components/SettingsDialog.svelte @@ -244,7 +244,7 @@ class="flex w-full items-center justify-between gap-2 px-3 py-2.5 text-left text-xs text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground" onclick={() => { open = false; onopenabout() }} > - About DB Studio + About Stroke
diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index f5bb1f6c..a0f79d6b 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -74,7 +74,7 @@ ); // Section open/collapsed state — persisted across sidebar toggles - const SIDEBAR_EXPAND_KEY = 'db-studio:sidebar-sections' + const SIDEBAR_EXPAND_KEY = 'stroke:sidebar-sections' function loadSidebarSections() { try { const raw = localStorage.getItem(SIDEBAR_EXPAND_KEY) @@ -100,7 +100,7 @@ $effect(() => { saveSidebarSection('matViews', matViewsOpen) }) // ── Pinned tables ───────────────────────────────────────────────────────── - const PINNED_KEY = 'db-studio:pinned-tables' + const PINNED_KEY = 'stroke:pinned-tables' function loadPinnedAll() { try { @@ -136,7 +136,7 @@ } // ── Display preferences ─────────────────────────────────────────────────── - const DISPLAY_PREFS_KEY = 'db-studio:sidebar-display' + const DISPLAY_PREFS_KEY = 'stroke:sidebar-display' function loadDisplayPrefs() { try { const raw = localStorage.getItem(DISPLAY_PREFS_KEY) diff --git a/src/lib/components/SqlEditor.svelte b/src/lib/components/SqlEditor.svelte index 32872d28..391f60be 100644 --- a/src/lib/components/SqlEditor.svelte +++ b/src/lib/components/SqlEditor.svelte @@ -5,7 +5,7 @@ import { registerMonacoSqlFormatter } from '$lib/format-sql.js' import { registerMonacoSqlCompletion } from '$lib/monaco-sql-complete.js' import { - defineDbStudioMonacoThemes, + defineStrokeMonacoThemes, monacoThemeId, readEditorFontOptions, } from '$lib/monaco-themes.js' @@ -70,7 +70,7 @@ }, { capture: true, passive: false }) // Still register the action so it appears in Monaco's command palette (F1) ed.addAction({ - id: 'db-studio.run-query', + id: 'stroke.run-query', label: 'Run Query', run: () => run(onmodenter), }) @@ -102,7 +102,7 @@ onMount(() => { configureMonacoWorkers() - defineDbStudioMonacoThemes() + defineStrokeMonacoThemes() registerMonacoSqlFormatter(monaco) registerMonacoSqlCompletion(monaco, () => schemaHints) if (!container) return diff --git a/src/lib/components/StudioShell.svelte b/src/lib/components/StudioShell.svelte index 4e9dadf2..7f751143 100644 --- a/src/lib/components/StudioShell.svelte +++ b/src/lib/components/StudioShell.svelte @@ -181,9 +181,9 @@ if (map.size > COLUMNS_CACHE_MAX) map.delete(/** @type {string} */ (map.keys().next().value)) } - const ONBOARDING_KEY = 'db-studio:onboarded' - const SAMPLE_SEEDED_KEY = 'db-studio:sample-seeded' - const SAMPLE_DB_ID = 'db-studio:sample-sqlite' + const ONBOARDING_KEY = 'stroke:onboarded' + const SAMPLE_SEEDED_KEY = 'stroke:sample-seeded' + const SAMPLE_DB_ID = 'stroke:sample-sqlite' let showOnboarding = $state(false) // Dev-only: Alt+Shift+O resets and re-shows the onboarding. Dead code in prod. @@ -265,12 +265,12 @@ let loadingTables = $state(false) // AI Mode — full-screen chat, hides sidebar and tabs - function loadAiMode() { try { return localStorage.getItem('db-studio:ai-mode') === '1' } catch { return false } } - function saveAiMode(v) { try { localStorage.setItem('db-studio:ai-mode', v ? '1' : '0') } catch {} } + function loadAiMode() { try { return localStorage.getItem('stroke:ai-mode') === '1' } catch { return false } } + function saveAiMode(v) { try { localStorage.setItem('stroke:ai-mode', v ? '1' : '0') } catch {} } // Hidden columns — persisted per connection+schema+table /** @param {string} connId @param {string} schema @param {string} table */ - function hiddenColsKey(connId, schema, table) { return `db-studio:hidden-cols:${connId}:${schema}.${table}` } + function hiddenColsKey(connId, schema, table) { return `stroke:hidden-cols:${connId}:${schema}.${table}` } /** @param {string} connId @param {string} schema @param {string} table @returns {Set} */ function loadHiddenCols(connId, schema, table) { try { const v = localStorage.getItem(hiddenColsKey(connId, schema, table)); if (v) return new Set(JSON.parse(v)) } catch {} @@ -503,7 +503,7 @@ void mcpUpdateConnections(savedConnections, activeConnectionId || null) }) - const QUERY_HISTORY_OPEN_KEY = 'db-studio:query-history-open' + const QUERY_HISTORY_OPEN_KEY = 'stroke:query-history-open' function loadQueryHistoryPref() { try { return localStorage.getItem(QUERY_HISTORY_OPEN_KEY) === '1' @@ -2805,7 +2805,7 @@
-

DB Studio

+

Stroke

Connect to PostgreSQL to browse schemas and table data.

diff --git a/src/lib/components/UpdateDialog.svelte b/src/lib/components/UpdateDialog.svelte index 51e3fc74..320c4988 100644 --- a/src/lib/components/UpdateDialog.svelte +++ b/src/lib/components/UpdateDialog.svelte @@ -1,41 +1,40 @@ + +{#if releasePage} + void install()} + onrestart={() => void restart()} + onclose={() => (releasePage = false)} + /> +{/if} + + {#if visible} - {#if status === 'available'} - -
e.target === e.currentTarget && (dismissed = true)} - > -