Repository navigation
Release 2.1.0: code editor for cells, steadier startup, ER diagram zoom - #87
Merged
Merged
Conversation
The connect dialog read the clipboard the moment it opened and again on every window focus and visibility change. On macOS each read can raise the system paste prompt, so simply opening the dialog, or switching back to the app, asked for permission the user never meant to give. The read now happens on a click into the paste bar. Focus alone does nothing, so the bar still takes focus on open and Cmd+V then Enter works as before. Typed text still wins and a string that was cleared stays cleared; the read is skipped entirely when the bar already holds text.
App mount faded the page in while AppLockGate was still reading the keychain (up to 1.5s), so what faded in was an empty page. The shell then popped in unfaded on top of it, and the reconnect overlay after that. The page now stays at opacity 0 until the first screen that is actually finished: the shell once it has decided between onboarding, the connect dialog, the welcome screen and the reconnect overlay, or the lock screen, the trial screen or the crash screen. A 2.5s failsafe reveals it anyway, so the window can never be left blank.
A screen recording of the release build, read frame by frame, showed two things dev hides (its FPS overlay keeps WebKit compositing every frame). Opening the related-rows dock shortens the grid, and resizing the canvas backing store lets WebKit show the new bitmap in the old box for one frame: the grid, stretched by old/new height. The canvas now stays as tall as the viewport has been, clipped by the scroll container, and never taller than the content, so it cannot add scroll range the table did not already have. A shrinking viewport no longer resizes it at all, and the frame after any viewport change is painted in the same tick so a closing dock never uncovers stale pixels. Opening a table swapped the whole grid for a loading placeholder, which also destroyed the canvas, and a fetch that landed just past the delay flashed a spinner for a single frame. The grid now stays mounted with the placeholder drawn over it; for the first 220ms the canvas holds the last table's pixels so a quick switch lands as one swap, and every spinner (grid, empty result, related rows) fades in so one frame of it is invisible. The toolbar's disabled dim waits 200ms too.
The database name, engine, schema, table count, host and user repeated what the title bar and status bar already say, and pushed the tiles down. The new tab now opens on the logo, Quick access and the tiles.
npm run tauri:build:mac and tauri:build:win (and tauri:build:local for mac-x64, linux or the host) build installers on your own machine instead of in CI. Windows builds natively on Windows and is cross-compiled from macOS or Linux with cargo-xwin; the script checks for NSIS, clang-cl, llvm-rc and cargo-xwin, says how to install what is missing, puts the keg-only Homebrew llvm on PATH, and lists the finished artifacts. Bundled DuckDB does not compile under clang-cl as shipped: it marks deleted functions dllexport. DUCKDB_STATIC_BUILD empties that macro, which is right for a static link, and it goes in through CL because cargo-xwin overwrites CXXFLAGS_<target> and _CL_ lands after "--" as a file name.
The paste bar in the connect dialog called navigator.clipboard.readText(), which is permission gated: WebView2 puts a "wants to see text and images copied to the clipboard" Block/Allow panel over the app the first time it runs. The bridge in src/lib/clipboard.js already routed reads through the Tauri plugin, but it fell back to the native web API whenever the plugin read threw, and that throws routinely on an empty clipboard or one holding an image. So the panel appeared anyway. readClipboardText() now reads only through the plugin and returns '' for anything else: empty clipboard, non-text, or browser dev where the plugin isn't bundled. The native read fallback is gone, and navigator.clipboard.readText is bound to the same helper so a stray call from a dependency can't raise the dialog either. Writes are untouched, they never prompt. Also refreshes package-lock.json, which was stale at 1.24.0 against a 2.0.0 package.json.
The test that every component imports the Svelte lifecycle functions it
calls matched `mount() {` in a CodeMirror panel object as a call to
Svelte's mount(), and failed on a component that never uses it. A name
followed by a parameter list and a body is a method definition, so the
call pattern now skips that shape.
CodeEditor.svelte wraps CodeMirror 6 for JSON, HTML, SQL snippets, stack traces and long text: line numbers, active-line highlight, its own find and replace panel, and language modes for JSON and HTML. CodeMirror owns its layout, caret, selection and undo, and it is a fraction of Monaco's size, which is the editor the cell panel deliberately does not load for one cell.
The cell panel faked a code editor with a <textarea> over a coloured copy of the text. That only lines up while both boxes lay text out to the pixel identically, and they drift on wrap width, scrollbar space and font fallback, so the caret ended up over the wrong character. The raw pane is now a CodeEditor. The JSON tree pane is gone. The editor already shows the structure (colours, folding, brackets), and the tree re-ran JSON.parse over the whole value on every keystroke to draw a second copy of it. What is left of it is the notice that the text on screen is not the whole value. Structured text opens unwrapped so the gutter numbers its lines; prose keeps wrapping, and Alt+Z flips it either way. Focus waits for the lazy-loaded editor to exist, so opening the dock really does put the caret in it.
Inside the cell panel's CodeMirror (its find bar included), Mod+F is the editor's own find and Mod+H / Mod+Alt+F its replace. The shell's hotkeys took those keys first and pulled focus up to the table's row search or the find-and-replace panel, and the sidebar's Mod+F handler moved it to the table filter. All three now step aside for a target inside .cm-editor, and the sidebar also respects a key the editor already marked handled.
inManagedEditor() matched anything under .cm-editor, which includes the inputs in CodeMirror's search panel, so those fields got no word delete, no undo and no shield from the global hotkeys. CodeMirror manages only its text surface, .cm-content, and that is now the element that counts.
On WebKitGTK, Shift+Tab reaches the page as GTK's ISO_Left_Tab keysym, not "Tab", so the key-name check skipped the row handler and the browser's own Shift+Tab jumped to the filter instead of the previous row. The check now accepts the physical key (e.code) too. Row navigation also found the current row through document.activeElement. The row's checkbox span is tabindex="-1", so a click near the icon focused the span, which is in no row list, and every Tab or arrow from there fell through. It now uses the row the event came from.
Same WebKitGTK ISO_Left_Tab keysym as the sidebar: Shift+Tab in the status bar's database picker and in the saved-connection list compared e.key only, so on Linux it moved focus out of the list instead of up it. Both now accept e.code === "Tab" as well.
Saved-connection rows styled :focus, so clicking a row left the focus outline on it as if it had been reached with Tab. The row now uses :focus-visible. While that outline is up, the row's selection bar fades out: the outline is drawn 2px inside the row, so the bar sat right against its edge and read as a doubled border.
The columns menu had one toggle that read "Show all" whenever anything was hidden. Incoming foreign keys load after the table opens, so hiding everything before they landed left the relationship columns visible with something already hidden, and the button flipped to "Show all" with no way left to hide them. The menu now offers each action whenever it means something: Hide all while any real or relationship column is still visible, Show all while any is hidden, both at once in between.
Every lookup replaced the panel's data with an empty loading result first, so clicking another row's key blanked the table, headers and all, until the result landed. The panel now draws the last settled result until the next one arrives and dims it once the load has run long enough to show a spinner, so a lookup reads as one swap. The held result is only reused for the same relationship; another one would be the wrong table.
- The confetti draws on a canvas the form owns rather than the one canvas-confetti appends to <body>, which outlived the screen: the app mounted underneath and the last particles fell across the workspace. - The confetti chunk is fetched when the key field is first touched, not on success, so no chunk load sits between "Activated" and the first particle. - "Verifying" only shows once a check has taken 200ms; a local check answers in a frame and the label swap was its own flicker. - A previous error stays up until the next result replaces it, instead of collapsing the line and jumping the button on every attempt. - New onsuccess (key accepted) and onactivated (celebration over) callbacks let the caller move on at the right moment, between 1.2s and 3.2s after success. - The field and button use the shared Input and Button components.
activateLicense() refreshes the license status before it resolves, so the gate saw "not blocked" mid-activation and mounted the whole app in the same tick the confetti started. That made the celebration stutter and hid the "Activated" state it was for. LicenseGate now holds the trial screen once it has shown, until the screen reports it is done. TrialExpiredScreen turns its copy into a welcome when the key is accepted, fades out when the celebration ends, and then hands over. The copy is also two short lines (what to do, then the reassurance) instead of one sentence balanced into three.
The label sat in the header, a full section gap away from the tiles, so it read as a caption for the logo. It now sits in the same gap-2 column as the tiles, the way "Jump to" sits with its list, and both labels use the same tighter tracking. The column widens to max-w-xl so the four tiles have room.
The page boots at opacity 0 and stays there until its first finished screen, and a transparent root showed the webview's white backdrop for the whole of startup. The boot script in index.html now sets <html>'s background inline to the base light or dark --background (#f7f7f7 / #080808), the same values as LIGHT_SURFACE / DARK_SURFACE in lib.rs, so window, webview and page all come up the same colour. Inline because the theme stylesheet arrives with the bundle, well after the first paint.
What shows before the page paints is the webview's own backdrop, not the window background behind it, and it defaults to white on every engine. Only macOS had it set (underPageBackgroundColor). set_webview_backdrop() now covers all three: - WebView2: ICoreWebView2Controller2::DefaultBackgroundColor, queried for rather than assumed, so an old runtime keeps the default instead of failing. - WebKitGTK: webkit_web_view_set_background_color. It runs at startup and again when the OS flips light/dark. webview2-com, windows-core, webkit2gtk and gdk are already in the tree through tauri/wry and are pinned to the versions wry resolves, so no second copy of either binding gets linked.
A window visible from build() put every pre-paint layer on screen in turn: the host surface, the webview backdrop and the maximize resize. On Windows that read as the window flicking between two blacks (#080808 and #111111) for about half a second before the app appeared. The main window and every window opened with open_new_window are now built with visible(false). revealApp() shows the window through a new reveal_window command before it fades the page in. It has to show first: a hidden WebKitGTK window never fires requestAnimationFrame, so the fade would wait forever. reveal_window is an app command rather than the core window.show because the capability file only grants core window permissions to the "main" label. tao keeps MAXIMIZED across the hide, and show() issues SW_SHOW then SW_MAXIMIZE, so the OS still records a real maximized state and minimize -> restore keeps working. A 4s Rust failsafe shows the window if no JS runs at all, on top of the existing 2.5s JS one.
A warm reconnect lands in a few hundred ms. Showing the ring and "Reconnecting to ..." at once flashed them for a moment and then faded them out over the shell, which on a cold start read as the window flickering. The overlay's plain surface still covers the wait at once; the ring and the label fade in only if the connect is still going after 350ms.
Revealing the window on the reconnect overlay meant three screens in about half a second: "Reconnecting", an empty shell with a skeleton sidebar, then the tables. A startup reconnect now keeps the window hidden until the connection is up and the table list has loaded, and falls back to showing the overlay after 700ms (RECONNECT_REVEAL_MS) so a slow connect never looks like nothing happened. Every other start (onboarding, no saved connection, disconnected last time, auto-reconnect off) reveals on its own first screen, and a failed, cancelled or timed-out reconnect reveals on whatever screen it ends on. The overlay's exit fade is 120ms, and it is skipped when the overlay leaves before the page has been revealed (isRevealed()): that fade would otherwise play over the shell right as the window appears.
Closing the window only hid it to the system tray, so every close left a live Stroke process behind, and each relaunch added another tray icon next to the old ones. The tray (its Open / Quit menu and the icon that swapped with the OS theme) and the hide-on-close handler are gone. Closing the last window now quits the process, and the exit hook still reaps the OmniRoute proxy so nothing keeps running in the background. Drops the tray-icon feature from tauri and the two tray images from the bundle resources.
Every wheel event zoomed the diagram, so a large schema could only be moved by dragging. Ctrl+wheel, the gesture people reach for to zoom, never reached the canvas at all: the global zoom blocker swallows it anywhere outside a [data-zoom-surface], and the canvas was not one. The canvas is now a zoom surface. Ctrl/Cmd + wheel (and a trackpad pinch, which arrives as ctrl+wheel) zooms at the cursor, a plain wheel pans, and Shift+wheel pans sideways on a mouse with no horizontal wheel. Line and page deltas are converted to pixels so a notched wheel moves a sensible distance.
Adds the CodeMirror cell editor, the ER diagram wheel controls, the startup and tray changes, the related-rows, columns-menu, keyboard and license fixes, and the new tab's Quick access label, next to the entries already there.
The notes step ran `node -e "..." VERSION="$VERSION"`, which hands node VERSION=2.0.0 as a script argument rather than an environment variable. process.env.VERSION was undefined, the script looked for a "## [undefined]" header, and every release fell back to "No changelog available for this release." VERSION is now exported before node runs. Also shortens the changeset to one plain line per change, and drops the em dashes from the install section of the notes template.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merging with
release:minorcuts 2.1.0: auto-release bumps the version, folds the changeset into CHANGELOG.md and tagsv2.1.0, and the tag builds and publishes the release.New features
npm run tauri:build:mac/tauri:build:winbuild installers locally. Windows cross-compiles from macOS or Linux.Fixes
Release notes
Every release said "No changelog available for this release." In the notes step,
node -e "..." VERSION="$VERSION"passed VERSION to node as a script argument, soprocess.env.VERSIONwas undefined and the lookup never matched. VERSION is now exported, and a local run of the step againstv2.0.0produces its full section.Checks
cargo checkandnpm run buildare clean.