@voltro/plugin-comments 0.83.0 → 0.83.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.
Files changed (2) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -39,6 +39,81 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.83.1] — 2026-10-04
43
+
44
+ ### Added
45
+
46
+ - **The devtools capsule can be dragged anywhere and snaps into place** — `@voltro/devtools`
47
+
48
+ The closed devtools capsule is no longer fixed at the bottom centre. Drag it anywhere on the page; within 48px of a corner or an edge midpoint it snaps there, and outlines of those eight slots show while it is being dragged. The placement is remembered with the console's dock settings, follows another tab that moves it, and is kept relative to the viewport, so a resized window never pushes the capsule off screen. A capsule in a corner stays flush with it while its width changes between the status chips and a progress message. A press that travels less than 4px is still a click and opens the console.
49
+ - **Devtools push keeps dialogs beside the console; the console gets its own window** — `@voltro/devtools`
50
+
51
+ *Push the page* now lays the page out in the space beside an edge-docked console, fixed-position elements included. It used to pad the document, so dialogs, drawers and toasts still covered the full window and centred under the console. While pushed, the page scrolls inside `<body>` instead of the window (the scroll position is carried over and back), and `100vw` and media queries still describe the whole window.
52
+
53
+ The dock menu has a new position, **Own browser window** (`dock: 'external'`). It moves the console into a separate browser window and leaves the page untouched. Closing either side closes the console. A browser opens such a window only on a click, so after a reload the overlay returns as the capsule and the next click reopens it. A blocked pop-up is logged. The previous *Separate window* position is now labelled *Floating window*.
54
+
55
+ `Dock` gains `'external'` and `DevtoolsStrings.shell` gains `dockExternal` and `externalBlocked`. Both are additions: `initialDock` accepts one more value, and `strings` overrides stay partial.
56
+ - **Embeddings through the AI Gateway** — `@voltro/ai`, `@voltro/voltro`
57
+
58
+ `embed` / `embedMany` accept `{ name: 'gateway', model: 'openai/text-embedding-3-small' }` (and `AI_EMBED_PROVIDER=gateway`), so an app that routes its model calls through the gateway no longer needs a direct provider key for its embeddings. `providerOptions`, `expectedDimensions` and an `apiKey` / `baseURL` override work as for the other providers. `EmbeddingProviderName` gains `'gateway'` — a wider union of a config input; only an exhaustive `switch` over it in app code would need the new case.
59
+ - **Gateway media responses are read under a byte budget** — `@voltro/ai`
60
+
61
+ Image, video, speech and transcription calls on a `gateway` provider read the whole response body, and a gateway provider config had no way to bound it. A declared length over the budget is now refused before reading and an undeclared one is cut off at it, as an `AiError` naming the budget: `maxResponseBytes` on the gateway provider config, else `VOLTRO_AI_MEDIA_MAX_RESPONSE_BYTES`, else 128 MiB.
62
+ - **Offloaded calls refuse where the run cannot park, before submitting** — `@voltro/workflow`, `@voltro/ai`
63
+
64
+ `SuspensionUnavailable` (`@voltro/workflow`) lets a host declare that the code it runs is re-run rather than replayed, so nothing in it can park. Inside such a scope `mediaStep`, every `offload: true` call and `awaitSignalSuspending` die with `WorkflowCannotPark` before they enqueue anything. Previously such a call submitted the paid job, could not wait for it, and its result was read by nothing.
65
+
66
+ ### Fixed
67
+
68
+ - **Agentic flow steps always end, and say which agent and media they used** — `@voltro/plugin-ai-flows`
69
+
70
+ An agentic run could finish `succeeded` with steps still `running`: a tool interrupted or still outstanding when the planner finished never recorded an outcome. Every step now ends, `failed` with the reason. The planner's tools run under `SuspensionUnavailable`, so an offloaded media call from a tool refuses before submitting a job nothing would read. `pick_structured` asks for a schema strict structured outputs accept (the object travels as a JSON string), where it failed on every OpenAI model. `EngineDeps.listAgents` names the agents `run_agent` may call, the suggested plan no longer writes a step type like an agent name, and each step carries `agentRef`, `modality` and the planner's `purpose` as `description`.
71
+ - **A boot that meets a held migration lock still converges reactive triggers** — `@voltro/cli`
72
+
73
+ On PostgreSQL, each boot checks the change triggers that carry reactivity across instances and installs missing ones. When another process held the migration lock, the boot skipped the check, on the theory that the holder would install them. The holder could be doing something else entirely — two replicas booting onto a fresh schema each checked while the other held the lock for its own migration step — so neither installed the triggers. Writes then never reached the other replica's live queries until a restart. The boot still does not wait on the lock. It now re-checks in the background for about two minutes until the triggers are in place, and warns with `voltro db apply` as the fix if the lock stays held throughout.
74
+ - **A declared charset converts tables before new tables reference them** — `@voltro/database`
75
+
76
+ Declaring `charset: 'utf8mb4'` on MySQL or MariaDB planned a new table — created in `utf8mb4` — before converting the tables its foreign keys point at, and the engine refused it (`errno: 150 "Foreign key constraint is incorrectly formed"`, MySQL 3780) on every run. The conversion of a table the plan changes in no other way now runs before any table is created.
77
+ - **A failed db apply removes the framework tables it created** — `@voltro/cli`
78
+
79
+ When `voltro db apply` failed after creating `_voltro_*` framework tables, its undo passed over them and reported `nothing this run applied remains` over a database holding them. The undo now drops the framework tables the run created, names them in its report, and counts one it could not drop as a difference.
80
+ - **gatewayCostUsd counts what a BYOK call costs at the provider** — `@voltro/ai`
81
+
82
+ A gateway call that runs on the team's own provider key costs nothing at the gateway and is billed by the provider; the gateway reports that bill as `marketCost`. `gatewayCostUsd` read only `cost`, so every BYOK call reached the AI ledger, `costBudgetUsd` and budget gates as $0. When the successful attempt ran on BYOK credentials it now returns `cost + marketCost`, and `undefined` (priced from the table) when `marketCost` is missing.
83
+ - **A boot waits for a migration that is still running instead of giving up** — `@voltro/database`, `@voltro/cli`
84
+
85
+ Two processes migrating one schema — two pods of `voltro dev` after a saved schema change, two `voltro db apply` runs — took turns on the migration lock, and the second gave up after a fixed 30 s even while the first was still applying. The holder now touches its lock session every few seconds, and a waiter keeps waiting while it sees that, refusing only once the holder has been silent for `VOLTRO_MIGRATION_LOCK_TIMEOUT_MS` (default 30 s) — the sign of a process that died holding the lock. PostgreSQL, MySQL and MariaDB; on MySQL and MariaDB the lock also honours `VOLTRO_MIGRATION_LOCK_TIMEOUT_MS` now, which it ignored. When `voltro dev` stops after a failed boot because no terminal is attached, its log line now names `VOLTRO_DEV_KEEP_ALIVE=1`.
86
+ - **Every package declares the packages its built code imports** — `@voltro/cli`, `@voltro/data-transfer`, `@voltro/plugin-atlassian`, `@voltro/plugin-cdc-out`, `@voltro/plugin-comments`, `@voltro/plugin-storage`, `@voltro/testing`
87
+
88
+ `voltro build` of an api on MySQL, MariaDB, SQL Server or SQLite failed with `Could not resolve "pg"` once nothing else in the project depended on `pg`: the CLI imports `pg` for its PostgreSQL-only features and did not declare it, and the previous mail plugin had brought it in by accident. The CLI now depends on `pg`, resolves `vitest` and the PostgreSQL driver from the app, and six more packages declare what they import (`@voltro/runtime`, `@voltro/client`, `@effect/sql`; `@voltro/testing` peers `vitest` and optionally `react-dom`) — each of which resolved only while another package in the project happened to bring it. If you added `pg` to a non-PostgreSQL api only to make the build pass, you can remove it.
89
+ - **The mail and rate-limit database stores pass the boot permission audit** — `@voltro/plugin-mail`, `@voltro/plugin-ratelimit`
90
+
91
+ In 0.83.0 the mail plugin's `'postgres'` suppression and idempotency stores, and the rate limiter's `store: 'postgres'`, declare their tables through `extendSchema` — and neither plugin declared the `store:write` permission that declaring tables requires. The boot permission audit is fatal, so every app choosing one of those stores refused to start (`Plugin permission audit failed`, readiness never 200). Both plugins now declare `store:write` exactly when they contribute tables. Until you update, an app can grant it itself: `{ ...mailPlugin({ … }), permissions: [...(plugin.permissions ?? []), 'store:write'] }`.
92
+ - **An interrupted PostgreSQL query no longer takes a pool connection with it** — `@voltro/sql-postgres`
93
+
94
+ On PostgreSQL, a query interrupted while it waited for a pool connection — a timeout, an aborted request, a lost race — left the queue handing the next free connection to a request nobody was waiting for, and nothing gave it back. Each such interruption removed one connection for the life of the process, so a busy pool shrank until it served nothing: operations in flight above the pool size, every backend `idle`, `Failed to acquire connection for transaction` on every call, no recovery without a restart. The wait is now cancellable: a connection that arrives for an interrupted query goes straight back to the pool.
95
+ - **The mail plugin's nodemailer range installs with npm** — `@voltro/plugin-mail`
96
+
97
+ Every published `@voltro/plugin-mail` up to 0.83.0 named its optional `nodemailer` dependency as `'^10.0.10'` — quotes included — so `npm install` refused the package with `Invalid tag name`, and a package manager that skips a failing optional dependency left the SMTP provider without `nodemailer`. The range is `^10.0.10` again, and the publish step now refuses any dependency whose spec is not a registry range.
98
+ - **Readiness runs its database ping under voltro dev too** — `@voltro/cli`
99
+
100
+ `/internal/readiness` runs a `SELECT 1` through the app's pool on SQL stores, so a process whose pool cannot hand out a connection answers `503`. Only `voltro serve` installed that ping: a deployment running `voltro dev` in its pods kept a pod with a dead pool at `200` for an hour. Both boot paths install it now.
101
+ - **Mail and rate-limit store tables from before 0.83.0 migrate on upgrade** — `@voltro/database`, `@voltro/plugin-mail`, `@voltro/plugin-ratelimit`
102
+
103
+ Before 0.83.0, the `'postgres'` suppression, idempotency and rate-limit stores created their tables at runtime. They were keyed on `(tenant, email)`, `(tenant, key)` and `key`, with epoch-millisecond `created_at` / `touched` columns. Since 0.83.0 the same table names are declared with a numeric `id`, a unique on the old key and timestamp columns. A database that still had the old tables got `auto-migrate: REFUSED — 6 blocked operation(s)` from `voltro dev` and `voltro db apply`, and the api did not boot.
104
+
105
+ These tables now migrate in place. Suppressions with their reasons, recorded sends and live rate-limit buckets are kept. `result_id` / `result_provider` are renamed to the camel-cased columns. `created_at` / `touched` are dropped through `dropped()` markers and replaced by `createdAt` / `touchedAt`, which start at the upgrade time. `touchedAt` now has a default. The `rateLimitTable` declaration gains the `touched` marker; nothing calls it with values, so the declaration change breaks no caller.
106
+
107
+ The planner learned what this needed, for any table: - A numeric `id({ scheme: 'numeric' })` added to a table keyed some other way is `needs-default` on PostgreSQL and MySQL / MariaDB. The auto-increment primary key numbers the existing rows, and the old primary key is dropped first. - A one-column primary key is dropped as a constraint (`DROP PRIMARY KEY` on the MySQL family). - A table created fresh gets no column for a `dropped()` marker. - No query selects a `dropped()` marker's column. On MySQL / MariaDB, SQLite and SQL Server the single-statement eager load of such a table asked for the dropped column and failed (`many()` and `one()` included); on PostgreSQL a `manyToMany` load with `junction: true` did. Each such call then ran again on the multi-query loader, paying both paths, and that loader returned the marker in `_junction`. Once the drop had applied, keeping the marker was therefore not harmless; now it is. - The refusal's rename hint names the added columns instead of printing `[object Object]`.
108
+ - **Two 0.83.0 upgrade notes stay silent for projects they do not reach** — `@voltro/cli`
109
+
110
+ `voltro update` across 0.83.0 listed every PostgreSQL api config under the rate-limiter store note (`store: 'postgres'` is also how an app names its database) and every `reviewStatus` or `@voltro/cms` mention under the CMS review note, including lockfiles and projects without the CMS. The rate-limiter note now counts `store: 'postgres'` only in a file that mentions the rate limiter, and the CMS note applies only to a project whose `package.json` declares `@voltro/cms`.
111
+ - **A workflow runner whose pool serves nothing gives its shards up** — `@voltro/workflow`
112
+
113
+ A runner's heartbeat and shard locks ride their own reserved connection, so a process whose connection pool had stopped serving queries kept heart-beating and kept every shard it owned — and a durable run started on one of them stayed `pending` until the process was replaced. Each runner now probes its pool on the lock-refresh cadence (`VOLTRO_WORKFLOW_FAILOVER_HEARTBEAT`); when the pool has not served a query for three intervals it releases its shard locks, stops its heartbeat and logs why, and another runner takes the shards. It takes part again once its pool has answered for as long again — one answer from a pool that frees a slot for a moment is not a recovery.
114
+
115
+ ---
116
+
42
117
  ## [0.83.0] — 2026-10-03
43
118
 
44
119
  ### ⚠ BREAKING
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-comments",
3
- "version": "0.83.0",
3
+ "version": "0.83.1",
4
4
  "description": "Comment threads on any anchor your app can name — replies, resolve/reopen, reactions, per-subject unread — live over the existing reactivity channel, with no second push mechanism. Access delegates to your app's own guard (access.viaEntity) or a scope and fails closed when neither is declared; @-mentions are re-filtered to the caller's tenant on search and re-validated at create, then delivered through plugin-notifications when it is installed. useComments / useThread / useMentionSearch in /web, an ejectable <CommentsThread> in @voltro/ui, plus inspect endpoints and a dashboard panel.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -48,9 +48,10 @@
48
48
  "node": ">=24.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@voltro/database": "0.83.0",
52
- "@voltro/plugin-notifications": "0.83.0",
53
- "@voltro/protocol": "0.83.0"
51
+ "@voltro/client": "0.83.1",
52
+ "@voltro/database": "0.83.1",
53
+ "@voltro/plugin-notifications": "0.83.1",
54
+ "@voltro/protocol": "0.83.1"
54
55
  },
55
56
  "peerDependencies": {
56
57
  "effect": "^3.22.2",