Skip to content

0.16.0

Choose a tag to compare

@cevheri cevheri released this 17 Sep 01:46
· 88 commits to main since this release
Immutable release. Only release title and notes can be modified.

A database object model, on every engine

The schema sidebar was one flat list of tables carrying a plain string name, which is why views and materialised views were never listed on PostgreSQL and why a table named a.b in public was generated as "a"."b". Providers now declare their container levels and their object kinds as data, so a ClickHouse dictionary, a Druid lookup, an Oracle package and a Redis function library each reach the tree under the engine's own word without a core change, and an object address is a path array rather than a joined string.

The tree is lazy in the sense that costs the user something: opening a deferred connection issues no catalog query at all, and a large folder mounts only the rows in view plus a small overscan, measured in the browser at 24 DOM rows for a 5,001-object Oracle folder.

This is the change that moves the minor version. /api/db/schema, /api/db/schema/list, /api/db/schema/relations and /api/db/schema-snapshot, and the TableSchema type exported from the library surface, are removed rather than deprecated. The object routes under /api/db/objects replace them, though not one call for one: a schema snapshot is now two reads of the object surface.

Read an object's definition, and edit it where the engine allows

Open a view, routine, trigger, package, dictionary, search pipeline or Redis function library from the tree and its definition opens in a read-only editor tab, on fifteen of the seventeen type-ids. The tab caption says what the text actually is, because the engines do not agree: stored as submitted, rebuilt by the engine from its catalog, or a structured definition rendered as JSON, and whether it is a complete statement or only a body. Where an engine refuses to hand the text over you get the engine's own sentence rather than a blank editor. The two type-ids with no definition surface, Apache Druid and the embedded LibreDB store, offer no Source tab at all, with the reason recorded per object kind in their provider docs rather than as a blanket "not supported".

On PostgreSQL functions and procedures, Trino functions and Redis function libraries the definition can also be edited and applied. Through the HTTP routes the apply step takes only a plan the server minted and sealed against that connection, so the bytes the plan pins are the bytes the engine receives; a host driving a provider straight from the library surface builds its own plan and carries that guarantee itself. On PostgreSQL the engine is asked to count the statements it was handed, so a semicolon rider appended to a definition or hidden behind a comment is refused with nothing sent. Semicolons inside a dollar-quoted body are one statement to that parser and stay legal, which is the engine's own reading rather than a hole in the check.

A transaction belongs to the account that opened it

On a shared connection, one Studio user could roll back another user's uncommitted work: transaction state lived on the cached provider instance rather than on the session. Measured through the product's own route on PostgreSQL 18.4, a user opened a transaction and inserted a row, an admin on the same connection id rolled it back, and the user's own commit then answered "No active transaction" with zero rows in the engine.

Ownership is now per authenticated account, with a five-minute idle lease, so an abandoned transaction frees the connection instead of locking everyone else out. Two tabs of the same account are one owner and still share a transaction, deliberately: nothing in the token or the cookie identifies a tab. Two related defects close with it: a multi-statement script that left a transaction open, and a plain read that could roll back a concurrent script whose commit had already reported success.

A second factor for the local login provider

Local email and password accounts can require a 6-digit TOTP code (RFC 6238), opted into per account with ADMIN_TOTP_SECRET and USER_TOTP_SECRET, or the chart values secrets.adminTotpSecret and secrets.userTotpSecret. The password alone never creates a session, and on a single replica an accepted code cannot be replayed inside its 90-second window. Both are asserted as security control 1.6 in docs/SECURITY.md, which also records the limit: the spent-code set lives in the application process, so across replicas a captured code can be replayed once per replica.

The secret is an environment variable rather than enrolled state on disk, because the image and the chart run on a read-only filesystem and a factor that silently degrades when the data directory is unwritable would be worse than no factor at all. A value that is not valid base32, or that decodes to fewer than the 128 bits RFC 4226 requires, stops login with a 503 naming the variable rather than quietly dropping the factor, and the chart applies the same test at install time through values.schema.json.

Serve Studio from a subpath

Studio can be built for a mount such as /tools/libredb and served behind a proxy that preserves the prefix. Root-relative API calls, editor assets, redirects and cookies all broke on that before. BASE_PATH is validated at build time and baked into Next routing, browser requests, metadata and Monaco, and the session and OIDC state cookies are scoped to the mount path and deleted with the same path.

Note the shape of this before planning a rollout: Next fixes basePath at build time, so the published image cannot be relocated with a runtime variable, and an operator wanting a subpath builds their own image with the Docker build argument. The chart's config.basePath prefixes the three default health probes so they keep answering; it does not re-route a prebuilt image. Reverse proxy rules for Nginx, Traefik, Ingress and HTTPRoute, and the OIDC callback URLs, are in docs/SUBPATH.md.

Agent mode: run history, and thirty-five measured models

A finished agent run appends to a per-account history index, and the rail's History surface lists the 50 newest finished conversations, newest first and paged by cursor, so yesterday's conversation can be reopened rather than only the one in front of you. Reopening reads the run's own ledger, so the index stays a pointer list and never becomes the authority on what happened.

The verified local model roster grew from twenty-eight to thirty-five, each measured clearing all six agent surfaces five consecutive times. Three drive fixes landed alongside and are gated behind no per-model setting, so they also reach models nobody has measured: a report hold guarded against running out of time but not out of turns, a near-miss field name reported as two faults instead of one rename, and a shortfall class that had no guidance sentence at all.

Getting results out

Any result can be copied to the clipboard in every format the Export menu already writes, so a handful of rows no longer goes to disk and back to be pasted into a message. The desktop grid gained a sticky control column that opens the vertical row detail view. That view existed only on small screens until now, which is exactly where a 200-column result is least likely to be met.

CSV import and result export offer comma, semicolon and tab delimiters with comma still the default, the Pivot Table exports its configured view as CSV or JSON, the Data Profiler exports its computed statistics, and the admin Audit tab exports its filtered operations and queries. Saved Queries gained JSON export and import, with the import validated whole before anything is written and collisions reported rather than overwritten.

Editor and connection workflow

Closing a query tab by misclick is recoverable: a toast offers Undo and puts the tab back before the neighbour it sat in front of, with its unsaved text intact. A ? key opens a keyboard shortcuts dialog whose command palette, run, format and new-tab rows are read from the registry the app installs them from, so those cannot drift; the rows for ? itself, tab strip navigation, the profiler's Escape and the object tree keys are listed as plain text, because none of them is a registry entry. Connections can be pinned as favourites, reordered by drag and drop, and duplicated from the editor, and each connection can carry its own query timeout instead of the fixed 60 seconds, with a blank field preserving that default. Deleting a connection asks for confirmation, and keystrokes are no longer overwritten while a large schema loads in the background.

Two scoping notes. The favourites and the order are storage collections, so they are per user on the sqlite and postgres storage providers and per browser on the default local one. The toast and the connection confirmations belong to the standalone app: the embeddable @libredb/studio shell mounts no toaster and wires connection delete to a no-op. The shortcuts dialog does reach an adopter, but only while the Data Profiler is open, which is the only thing that mounts it there.

Operators and distribution

The startup banner prints the bind address from HOSTNAME rather than always saying localhost, so a non-loopback bind prints a URL that answers instead of one that does not. A wildcard bind still prints a loopback URL, which is what the image and the chart do by default. The snap package stopped baking HOSTNAME, PORT, STORAGE_PROVIDER, STORAGE_SQLITE_PATH, NODE_ENV and NEXT_TELEMETRY_DISABLED into its manifest, where snap-exec applied them over the caller's environment and silently ignored the systemd drop-in the docs recommend. The DigitalOcean 1-Click first-boot script now writes AUTH_COOKIE_SECURE=false, as the AWS AMI always does and the Azure installer does for its :80 deployment: that droplet is reached over plain HTTP on port 3000, so the browser was discarding the session cookie and login looped back to the sign-in page with the correct password. A droplet created today still comes from the marketplace snapshot built on 2026-07-19, so that fix reaches users once the image is rebuilt from a current release and resubmitted.

Monitoring reads checkpoint timings from pg_stat_checkpointer on PostgreSQL 17 and newer, where they moved out of pg_stat_bgwriter; those servers used to report N/A and log a column error on every refresh. Table and database sizes on the MySQL wire no longer read 0 B when the engine answers a NULL INDEX_LENGTH, which is what StarRocks does for every base table. Storage provider initialization is shared across concurrent first calls, so a burst of requests against a cold pod initializes the configured store once instead of racing.

Contributors

Named for what they did, with the pull request and the issue it closed.

  • @SyedMuhamadYasir built the subpath deployment support end to end, from the build-time BASE_PATH validation to the cookie paths and the proxy documentation, and made schema-diff migration DDL speak the dialect it is aimed at (#662 closes #369, #659 closes #284).
  • @macjayz gave the published npm package the discovery metadata it was missing (#697, closes #664).
  • @7487 guarded the duplicated CI-gate paragraph in CONTRIBUTING so the two copies cannot drift (#663, closes #661).
  • @slsgzs-cloud showed the Create Table button on an empty database, which is exactly where it is needed (#655, closes #654).
  • @stgomoyaa put a confirmation prompt in front of deleting a connection (#698, closes #693).
  • @mikevillari landed eight fixes across the editor, the CLI banner, UI truncation, icon accessibility and the test runner (#706 closes #665, #707 closes #687, #709 closes #670, #711 closes #671, #713 closes #686, #714 closes #700, #715 closes #705, #791).
  • @Matthew-Selvam shared one in-flight storage provider initialization across concurrent first calls, so a cold pod initializes its store once instead of racing (#724).
  • @CunjieLee fixed the Trino session schema, gave the CI jobs timeouts and added the PWA web app manifest (#721 closes #653, #736 closes #684, #781 closes #677 and #680).
  • @nktnet1 turned on Elasticsearch fieldMultiValueLeniency so array columns come back instead of erroring (#723, closes #722).
  • @Vetri1706 made the query timeout per connection instead of a fixed 60 seconds, with a blank field preserving the default (#752, closes #743).
  • @TonMtt wrote the contributor guide on keeping a branch current (#735, closes #734).
  • @yusuf-gundogdu measured the agent model roster, thirty and then five more models with the refusals behind each of them, added a WebM copy of the demo recording so AppStream catalogs can show it, and fixed the DigitalOcean session cookie that made login loop on a plain-HTTP droplet (#755, #824, #797, #901).
  • @kaya-abdullah got Studio into the upstream listings for Trino, Cloudberry, YugabyteDB and DragonflyDB, and fixed the MySQL NULL INDEX_LENGTH that read as 0 B (#780, #801, #895, #897, #920).
  • @na12334 added CSV and JSON export to the Data Profiler (#759, closes #750).
  • @Hashir-Ashraf-Awan built the TOTP second factor for the local auth provider, including the base32 validation that fails loudly rather than dropping the factor (#779, closes #777).
  • @jabrailkhalil added the new query tab shortcut, then moved shortcut handling into one shared registry the docs are generated from (#782 closes #745, #803 closes #802).
  • @nightcityblade replaced the untracked workflow design references in the docs with ones that resolve (#793, closes #790).
  • @Abdu11ahBilal wrote the Urdu README (#783, closes #637).
  • @Asgabani built connection favourites and drag-to-reorder, query tab undo and the keyboard shortcuts dialog, fixed a sidebar metadata race, and corrected the CapRover template's version pin and its AI and rotation copy (#812 closes #694, #817 closes #748, #818 closes #747, #821 closes #746, #848 closes #846, #849 closes #819).
  • @Dharshni-gth wrote the root page meta description and corrected table detection in inline editing (#798 closes #678, #924 closes #836).
  • @Swarnabha753 added CSV and JSON export to the Pivot Table (#796, closes #749).
  • @Dharshini-RS03 added the result text wrapping toggle and kept Run Selection visible on hover (#810 closes #744, #838 closes #829).
  • @nycjay stopped keystrokes being overwritten while a large schema loads in the background (#809, closes #808).
  • @iAmAdheil wrote the Hindi README (#845, closes #595).
  • @koraysrn built agent run history, so a finished conversation can be listed and reopened (#830).
  • @InnoxCodes landed eight admin, login, docs and schema explorer fixes (#885 closes #877, #890 closes #876, #891 closes #875, #893 closes #860, #894 closes #854, #896 closes #855, #929 closes #857, #932 closes #861).
  • @niukanen1 made the startup banner print the address the server actually answers on (#847, closes #814).
  • @costajohnt classified OIDC login failures by type, with one message per class instead of one message for all of them (#871, closes #852).
  • @0utsights corrected the Keycloak ID-token role mapping setup (#904, closes #850).
  • @Rayan-and-beyond fixed four documentation defects: the translated README index, the runtime light theme, banner suppression and the DigitalOcean health check (#903 closes #859, #911 closes #858, #912 closes #866, #915 closes #905).
  • @YaoSong808 made Escape close the code generator (#892, closes #879).
  • @Mehmetalpertugtekin pointed the FLY.md README link at the Environment Variables section (#914, closes #868).
  • @DevvoLazza made the Windows ledger probes retry on a transient failure and tracked the DigitalOcean listing drift (#925 closes #900, #927 closes #910).

Upgrading

The schema API is the thing to check before upgrading. The four routes named above and the TableSchema type are gone, and the object routes replace them. Nothing else in this release needs an operator action: TOTP is opt-in per account through two environment variables or two chart values, and a subpath mount needs an image built with the BASE_PATH build argument, which the published image is not.

An adopter embedding the workspace from @libredb/studio has more to do than an operator. onSchemaFetch now returns DetailedObject[], with kind and path required on each, and the object tree is fed by a new required onObjectsFetch prop, so dropping TableSchema alone is not enough: a second reader has to be implemented.

Helm chart: 0.1.64

Three values are new since chart 0.1.61, the one that shipped with 0.15.0: secrets.adminTotpSecret, secrets.userTotpSecret and config.basePath, with matching secrets.existingSecretKeys entries. All three default to empty and the two TOTP env refs are optional, so a values file that sets none of them renders as it did before. artifacthub.io/changes carries ten entries, and containsSecurityUpdates is true this time: a transaction belonged to the cached provider rather than to the session, so any account reaching a shared connection id could roll back another account's uncommitted work.

Full changelog: 0.15.0...0.16.0