@voltro/ui-shadcn 0.78.0 → 0.79.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,119 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.79.1] — 2026-09-24
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **A charset conversion blocked by a soft-drop snapshot names the reclaim** — `@voltro/database`
|
|
47
|
+
|
|
48
|
+
A declared `store.charset` refuses to convert a table that a foreign key ties to a table the plan does not convert. When that other table is a soft-drop snapshot (`<name>__dropped_<stamp>`), the fix line offered three remedies that do not fit one: declaring it, converting it by hand, or leaving the charset undeclared. It now names `voltro db gc-snapshots --before <date>` with the date that covers the newest blocking snapshot, says that the date also removes every older snapshot, and names `voltro db restore-snapshot` for data that is still wanted.
|
|
49
|
+
- **The devtools capsule morphs into a status and back** — `@voltro/devtools`
|
|
50
|
+
|
|
51
|
+
When `voltro dev` reported a status such as "Compiling…", the closed capsule swapped its chips for the status in one frame and jumped to the new width. The swap is now a morph: the incoming content fades up while the capsule glides from its old width to the new one, and a status that changes mid-glide continues from the width reached. Under `prefers-reduced-motion` the swap stays immediate.
|
|
52
|
+
- **The devtools console animates out the way it came in** — `@voltro/devtools`
|
|
53
|
+
|
|
54
|
+
Opening the console slid it in from its dock edge (or scaled a floating window up), but closing removed it in one frame. Closing now plays the entrance backwards: the console slides out towards its edge, or the window scales down and fades, and the capsule returns once it has gone. The console ignores input while it leaves, a page pushed aside by it reflows only after it is gone, and under `prefers-reduced-motion` it still closes at once.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **Probes are answered while a large app's modules load at boot** — `@voltro/cli`
|
|
59
|
+
|
|
60
|
+
The app port answers the health probes from the start of the boot, but the process that answers them is the one booting. Loading the app's modules runs on Node's synchronous loader, so the whole run was one turn of the event loop and a probe waited for all of it: over 800 ms on a generated 1798-module app booting `voltro serve` from the production bundle, and over a second on a loaded machine. Loading now hands the thread back every 25 ms; on the same app, at load 14 on 12 cores, the slowest liveness answer during boot was 89 ms. `voltro dev` loads its modules through the same path.
|
|
61
|
+
- **`voltro update` notes are not silenced by a comment naming the option** — `@voltro/cli`
|
|
62
|
+
|
|
63
|
+
A `manual` codemod decides whether your project is affected by searching its files. In code that search counted comments: an api config that set neither anonymous-tenant option and explained why in a comment read as one that had chosen, and the note meant for it was skipped as "none matched your project". Commented-out code did the same. In `.ts`, `.js` and `.json` files (and their variants) comments no longer count; prose files — runbooks, CI definitions, shell scripts — are still read whole. This applies to every codemod a later `voltro update` runs, including the ones of versions you have not crossed yet.
|
|
64
|
+
- **`voltro update` notes search the whole workspace, not only the app** — `@voltro/cli`
|
|
65
|
+
|
|
66
|
+
`voltro update` runs from an app directory, and the search behind every manual note read only that app. A compose file at the workspace root still setting `VOLTRO_DEV_HEALTH_PORT`, or a tsconfig in another workspace member with a removed TypeScript 7 option, got "nothing in this repository matched" — though the TypeScript 7 note says it checks every tsconfig in the workspace. The search now covers the workspace the app sits in. The TypeScript 7 note also names each tsconfig line it found, and its hand-check `git grep` line matches `"moduleResolution": "node"` and the lower-case spellings (`amd`, `classic`, `es5`); it required `node10` before.
|
|
67
|
+
- **Correction: a compose baseline from before 0.79.0 needs `voltro baseline sync`** — `@voltro/cli`
|
|
68
|
+
|
|
69
|
+
The 0.79.0 note on probes said a compose baseline needs nothing because it regenerates on the next `voltro dev`. It does not: only `voltro baseline sync` rewrites those files, and a baseline synced before 0.79.0 sets `VOLTRO_DEV_HEALTH_PORT` and points its healthchecks at a port nothing listens on any more. `voltro update` now prints a correcting note for every project whose workspace still sets that variable, naming the lines.
|
|
70
|
+
- **`db apply` undo converts tables back and says exactly what still differs** — `@voltro/cli`, `@voltro/database`
|
|
71
|
+
|
|
72
|
+
On MySQL, MariaDB, SQLite and Turso a failed `voltro db apply` is undone rather than rolled back. When the run had converted tables to a declared `store.charset`, the undo planned the way back with that same charset, so no conversion could be planned in reverse — and the check behind "the database is as it was" asked the same charset-blind question, so the line printed over tables that stayed converted. The undo now converts each table back to the character set its string columns had before the run, and verifies it by reading every column back. A column the undo redefines also keeps its default: a string default read from MariaDB was quoted a second time. When an undo does not read as it was, the report now lists each remaining difference instead of asking you to compare by hand.
|
|
73
|
+
- **The compose dev image starts pnpm 11 and later as the non-root user** — `@voltro/cli`
|
|
74
|
+
|
|
75
|
+
The `compose` and `compose-mariadb` baselines' `docker/dev.Dockerfile` activated pnpm as root but never ran it. pnpm 11 and later download a native binary on their first run, into a directory the non-root user compose starts every service as cannot write, so `docker compose … up` stopped at the first service with `EACCES … pnpm-native….tgz`. The image now runs pnpm once while it is built. Run `voltro baseline sync` and rebuild the image to take it.
|
|
76
|
+
- **The closed devtools capsule glows again** — `@voltro/devtools`
|
|
77
|
+
|
|
78
|
+
When the overlay became a docking console, the closed state turned into a status capsule and lost the launcher's animated halo: a bright slice of brand violet circling the rim while a soft glow breathes around it. The capsule carries it again, traced along its pill outline. It steps aside while `voltro dev` reports a status, where the capsule's own pulse in the status colour takes over, and it stands still under `prefers-reduced-motion`.
|
|
79
|
+
- **`db gc-snapshots` drops snapshots that reference each other, and works on MySQL** — `@voltro/cli`
|
|
80
|
+
|
|
81
|
+
`voltro db gc-snapshots` dropped snapshots in catalog order. Two tables one plan soft-dropped keep the foreign key between them, so dropping the parent first failed with `1451` on MySQL and MariaDB (and on Postgres whenever the parent came first), every run, after the snapshots before it were already gone; a soft-dropped reference column failed with `1553`. The keys the snapshots hold are now dropped first, then the columns, then the tables, a child before its parent. A snapshot that a table outside the set still references is refused before anything is dropped, with each constraint named. On MySQL 8 the command, and `voltro db restore-snapshot`, found no snapshot at all, because the catalog answers in upper case there; both read it now.
|
|
82
|
+
- **`voltro update` lists the lines a manual note is about** — `@voltro/cli`
|
|
83
|
+
|
|
84
|
+
A `manual` codemod decides between "found" and "nothing in this repository matched" with its own check, and that check could be narrower than the search its note tells you to run. The note for probes on the app port told readers to grep for `internal/(liveness|readiness|startup)` but looked only for `VOLTRO_DEV_HEALTH_PORT`, so a project whose Helm values, compose healthcheck and smoke script probed app port + 1 directly got "nothing in this repository matched" while every one of them needed moving. A manual note can now list the lines it is about, and `voltro update` prints them under it (`── found in this repository (N lines) — check each:`, `file:line text`). The probe note lists every probe path and variable it finds, with the port beside each probe; the anonymous-tenant note names each api config that sets neither option. A note that lists a line is reported as found.
|
|
85
|
+
- **MariaDB: a charset, type or nullability change keeps the column's CHECK** — `@voltro/database`
|
|
86
|
+
|
|
87
|
+
On MariaDB a CHECK written on the column itself — how a table with `text().oneOf([...])` is created — is part of the column definition, and `MODIFY COLUMN` without it removes it. Every operation that redefines a column did exactly that: a `convert-charset` rewrite, a nullability, type or default change. The re-plan then proposed `add-check` for each, and the apply refused with "the migration did not converge", after every statement had run. Declaring `store.charset` over an existing schema hit it once per converted enum column. The rewrite now restates each column's CHECK, so it stays where it was; an explicit `drop-check` still removes one the declaration no longer has. MySQL keeps such a CHECK on its own and is unchanged. `emitConvertCharsetMysql` takes the CHECKs to keep as an optional last argument, so every existing call compiles as before.
|
|
88
|
+
- **`voltro serve` checks pending file migrations from its bundle, not from source** — `@voltro/cli`, `@voltro/database`
|
|
89
|
+
|
|
90
|
+
Before it boots, `voltro serve` refuses when a `migrations/<timestamp>_<slug>.ts` file has never run. `voltro build` did not bundle those files, so serve imported each from source — and, through it, a second copy of the framework's migration code. Measured on an app with two file migrations: about 290 ms during which the process answered nothing, after the port was already taking health probes. The build now bundles every file migration, and serve reads them from the bundle. `pendingFileMigrationIds` takes an optional module importer as its last argument, and `walkMigrationFiles` is exported; every existing call compiles as before.
|
|
91
|
+
- **`voltro update --wait` says what the registry answered, per package** — `@voltro/cli`
|
|
92
|
+
|
|
93
|
+
`--wait` reported every package it could not confirm as "not on the registry", whether the registry had answered 404 or the request had failed. It now names the answer with each package — `404: not published yet`, or `no answer: <status or error>` — while it waits, at the ceiling, and after a failed install, so a network error is never reported as a missing release. In `pnpm-workspace.yaml`, a `minimumReleaseAgeExclude` entry that pnpm 12.5 and later widened in place to a version union (`'@voltro/ai@0.78.0 || 0.79.0'`) was skipped without a word; a union holding the version being left now moves to the target like any other entry.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## [0.79.0] — 2026-09-23
|
|
98
|
+
|
|
99
|
+
### ⚠ BREAKING
|
|
100
|
+
|
|
101
|
+
- **Probes answer on the app port from the first moment, on every command** — `@voltro/cli`, `@voltro/runtime`, `@voltro/voltro`
|
|
102
|
+
|
|
103
|
+
`voltro dev`, `voltro serve` and `voltro start` now bind their app port as the FIRST thing they do once they know it, and answer `/internal/liveness` (200), `/internal/readiness` (503 until the app serves, then 200 with the dependency ping) and `/internal/startup` (JSON `{ phase, ready, elapsedMs, … }`, 503 → 200) there through the whole boot — a slow database connect, a fingerprint check or a cold migrate included. Every other request and every WebSocket upgrade gets `503 + Retry-After: 1` until ready, then the app serves on the same socket and the probe paths keep answering before routing. There is ONE server and ONE port, so a probe path means the same thing on every command; `voltro dev` no longer binds a second probe server on app port + 1 and no longer reads `VOLTRO_DEV_HEALTH_PORT`. Migration: point every probe of a pod running `voltro dev` at the app port and delete the variable — the codemod prints the exact steps and finds the variable in compose, Helm and CI files.
|
|
104
|
+
|
|
105
|
+
**`voltro update` carries you across this** — codemod `0.79.0/01_probes-on-the-app-port`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.79.0).
|
|
106
|
+
- **A rejected credential is refused on the default tenant policy too** — `@voltro/cli`, `@voltro/protocol`, `@voltro/voltro`
|
|
107
|
+
|
|
108
|
+
A request whose credential a strategy rejects (an expired or malformed token, a revoked session or key) now ends with `Unauthenticated` (`credential rejected: <reason>`) on every anonymous-tenant policy. Until now that held under `auth: { anonymousTenantId }` and `auth: { anonymousTenantRequired: true }`, but not for an app that sets neither. There, a procedure without a guard served the caller as an anonymous one, and an open query answered public data to a token that had just been refused. A caller who sends no credential is unaffected: it still resolves to the tenant its `x-tenant` header names, or to the development tenant. `composeAuthStrategies` gains `anonymousTenantDefault` for that default tenant. Unlike a `fallback`, it does not take over the rejected-credential decision. A client that kept sending a stale cookie or token and relied on the anonymous answer now receives `Unauthenticated`, which the error bus reports and `wireAuthRefresh` acts on.
|
|
109
|
+
|
|
110
|
+
**`voltro update` carries you across this** — codemod `0.79.0/03_rejected-credentials-refused-on-the-default-policy`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.79.0).
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
|
|
114
|
+
- **A 1366 over a 4-byte character says which config field fixes it** — `@voltro/database`
|
|
115
|
+
|
|
116
|
+
When MySQL or MariaDB refuse a write with `Incorrect string value: '\xF0…'` (errno 1366 over a four-byte UTF-8 lead byte — an emoji into a `utf8mb3` column), the store's `detail:` line now carries a `hint:` naming the column and the remedy: declare `store: { dialect, charset: 'utf8mb4' }` in `app.config.ts` and run `voltro db plan`. A 1366 for any other reason gets no advice about character sets.
|
|
117
|
+
- **The store's character set is declared once and every table follows it** — `@voltro/database`, `@voltro/cli`, `@voltro/devtools-ui`
|
|
118
|
+
|
|
119
|
+
On MySQL and MariaDB the framework's `CREATE TABLE` named no character set, so a `text()` column inherited whatever the database was created with while a `json()` column did not (MariaDB's `JSON` is `LONGTEXT CHARACTER SET utf8mb4`, MySQL's has none) — and on a `utf8mb3` database one table refused an emoji in its text column with errno 1366 and took it in its json column. `app.config.ts` now takes `store: { dialect: 'mariadb' | 'mysql', charset: 'utf8mb4' | 'utf8mb3' }`: every declared table is created with it as its default and, when a live table's string columns are stored otherwise, converted to it by a new `convert-charset` plan operation — a table rewrite that drops and re-creates the foreign keys touching the table, so a pair bound by a constraint converts together instead of one side failing with errno 150. Widening to `utf8mb4` is `safe`; narrowing to `utf8mb3` is `lossy` and waits for the destructive opt-in. json columns keep the engine's own charset (neither engine lets it be declared). Unset, nothing changes. A charset on any other dialect is refused at boot, and a `DB_DIALECT` override off the mysql family drops it with a logged line rather than silently.
|
|
120
|
+
|
|
121
|
+
The public signatures that changed only gain optional trailing parameters (`declaredSnapshot`, `emitNamespacedSchemaSql`, `emitNamespaceProvisionDdl`, the schema-emit options) and one union member (`convert-charset` in the migration operation kind), so existing calls compile unchanged. Only an exhaustive `switch` over that operation kind would need a new case.
|
|
122
|
+
- **The plan and the doctor name tables whose text cannot hold what json can** — `@voltro/cli`, `@voltro/database`
|
|
123
|
+
|
|
124
|
+
Without a declared store charset, `voltro db plan` prints — under the plan — the MySQL / MariaDB tables whose text columns are stored in a character set narrower than their json columns' `utf8mb4`, and the tables whose string columns are not all in one character set (a column converted by hand). `voltro doctor` has the same section, reading `information_schema`, with three outcomes that never print alike: not applicable off the mysql family, not checked with the reason when no database is reachable, and the findings — against the declaration when one is set. A healthy `utf8mb4` database reports nothing; in `--json` the section is `storeCharset`.
|
|
125
|
+
|
|
126
|
+
### Fixed
|
|
127
|
+
|
|
128
|
+
- **A handler's own `Unauthenticated` reaches the client typed, on any procedure** — `@voltro/protocol`, `@voltro/cli`, `@voltro/runtime`
|
|
129
|
+
|
|
130
|
+
`Unauthenticated` and `ScopeError` are now members of every procedure's wire error union — queries, mutations, actions, streams and workflow starts, whether or not the descriptor declares a guard — and `Unauthenticated` of every event's. They used to ride only a procedure with an enforced `guards:` entry, on the reasoning that an open procedure cannot produce them. It can: the auth middleware refuses an open procedure with a rejected credential exactly as it refuses a guarded one, and a handler may refuse on its own with `assertAuthenticated` or `requireScope`. On a procedure without a guard the server judged that handler-side refusal undeclared and collapsed it, so the browser received `{ _tag: 'InternalError', message: 'Unauthenticated: …' }` — the typed error's name inside a defect that no `_tag` match and no credential-refresh policy could see — and the generated client had no schema to decode the middleware's own refusal with. Both ends now carry the same union, so the value arrives as `{ _tag: 'Unauthenticated' }` on the failure channel over `POST /rpc` and over the WebSocket, and a gRPC surface maps it to `UNAUTHENTICATED` rather than `FAILED_PRECONDITION`. Measured under `voltro dev` and `voltro serve` with a validly signed, expired JWT driven through the real client over the socket.
|
|
131
|
+
- **`voltro build` walks an api app's module closure once, not once per handler file** — `@voltro/cli`
|
|
132
|
+
|
|
133
|
+
Each api artefact (`apiEntry.js`, the serve bundle) is preceded by a closure-discovery pass that only reads esbuild's metafile. It used to hand esbuild every discovery file as its own entry point, and without code splitting esbuild links every entry point separately, each on its own copy of the module graph, all at once. On a synthetic api app of 6,747 modules (4,514 handler files) that probe alone peaked at 4.3 GiB in the esbuild service — ten times the bundle it precedes — and it ran twice per build, so the whole build peaked at 5–6 GiB and could be refused by a shared host with less free memory than that. The probe now links one generated entry that imports every discovery file; the closure it finds is the same set. Measured on the same app the build's peak is under 2 GiB (esbuild 1.9 GiB, Node 0.5 GiB), at 3,048 modules 1.0 GiB, at 1,047 modules 0.9 GiB. The build page documents the expectation.
|
|
134
|
+
- **The file-migration `phase` rewrite reaches aliased, held and nested specs** — `@voltro/cli`
|
|
135
|
+
|
|
136
|
+
For a project crossing 0.78.0, the `0.78.0/01_file-migration-phase` rewrite now also finds `migration` imported under another name, a spec held in a local `const`, and migrations in subdirectories of `migrations/`. All three are shapes the runner discovers, and the rewrite used to skip them without a word. A spec built outside the call (by a helper, or imported) is not rewritten, because the change would land in code other callers share. Instead the rewrite names it by file and line. `migration()` still refuses a spec without a phase when the file loads.
|
|
137
|
+
- **A subscription refused in-band reaches the error bus and the refresh policy** — `@voltro/client`
|
|
138
|
+
|
|
139
|
+
A guard's refusal on a subscription travels in-band — a `{ _tag: 'error' }` event on that one stream, so a sibling subscription on the shared socket is not stalled. The subscription cache set the snapshot's error from it and told nobody else: the api's error bus, which `wireAuthRefresh` and `useOnRpcError` listen on, heard only streams that failed outright. So an expired credential on a read never asked the host for a fresh token and never reached an error reporter. The in-band value is now emitted to the bus with `source: 'subscription'` and `kind: 'handler'`, the same event a failed stream produces.
|
|
140
|
+
- **`voltro support collect` no longer fails on a temporary directory under src/** — `@voltro/cli`
|
|
141
|
+
|
|
142
|
+
The conflict-key inventory in `voltro support collect` found its files with a glob that skips dot-directories, then loaded them through a call that walked the directories again. So a temporary directory that an editor, a test run or a build tool creates and removes under `src/` could fail the whole collection with `Directory not found`, and an unreadable dot-directory failed it with `EACCES`. Only the files the glob returned are loaded now. A file removed between the two steps is skipped.
|
|
143
|
+
- **Tenant namespaces on MySQL / MariaDB provision without USE and take the charset** — `@voltro/database`, `@voltro/cli`
|
|
144
|
+
|
|
145
|
+
Provisioning a tenant namespace on the mysql family emitted `USE <database>` followed by unqualified DDL. MySQL 8.4 refuses `USE` in the prepared protocol (errno 1295), so under `tenancy.isolation: 'namespace'` no tenant could be provisioned on MySQL at all; on MariaDB the `USE` outlived its transaction on the pooled connection, which then served its next unqualified statement in the tenant database. The tenant DDL is now database-qualified, as the store's namespace view already was. The declared store charset reaches the tenant too: a new tenant database is created with it as its default and its tables carry it. A tenant provisioned before the declaration is named per namespace by `voltro db plan` and `voltro doctor` rather than silently left as it was.
|
|
146
|
+
- **`voltro update` moves the `@voltro/*` pins in `pnpm-workspace.yaml` too** — `@voltro/cli`
|
|
147
|
+
|
|
148
|
+
`voltro update` used to bump only the package.json files. A pnpm workspace that also names framework versions in `pnpm-workspace.yaml` was left behind in two ways. `overrides:` entries held the tree on the old release. And `minimumReleaseAgeExclude:` entries (`'@voltro/<pkg>@<version>'`, which admit a release younger than pnpm's release-age window) went stale, so pnpm refused the new release for that whole window. Both blocks now move with the bump. An entry moves only when it names exactly the version being left; an entry at any other version is printed as `kept`. A block written as a flow list is named rather than rewritten. The packages named there count for `--wait`, and the file is restored along with the package.json files when an install fails before anything lands.
|
|
149
|
+
- **`voltro update --wait` waits up to 45 minutes, longer than a release holds** — `@voltro/cli`
|
|
150
|
+
|
|
151
|
+
`voltro update --wait` polls the registry until every package it bumps is published at the target version, `@voltro/dashboard` included. It used to give up after 20 minutes. A release, though, holds `latest` for up to 30 minutes after its other packages finish uploading while `@voltro/dashboard` is still being built. So an update that started polling at the first upload could fail after a release that went on to complete normally. The ceiling is now 45 minutes, which covers the release's hold plus its upload phase. The update still reports the late packages while it waits, and on timeout it still restores the bumps byte for byte.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
42
155
|
## [0.78.0] — 2026-09-22
|
|
43
156
|
|
|
44
157
|
### ⚠ BREAKING
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/ui-shadcn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.1",
|
|
4
4
|
"description": "Voltro's first-party shadcn/ui kit: Tailwind v4 design tokens (light + dark), 30+ primitives, layout compositions, styled widgets for the @voltro/ui seam, and the canonical theme/language preference-cookie helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@radix-ui/react-toggle-group": "^1.1.19",
|
|
53
53
|
"@shikijs/langs": "^4.4.3",
|
|
54
54
|
"@shikijs/themes": "^4.4.3",
|
|
55
|
-
"@voltro/ui": "0.
|
|
55
|
+
"@voltro/ui": "0.79.1",
|
|
56
56
|
"class-variance-authority": "^0.7.1",
|
|
57
57
|
"clsx": "^2.1.1",
|
|
58
58
|
"shiki": "^4.4.3",
|