# Changelog All notable, user-facing changes to polyrouter, newest first. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow the frontend package's [semver](https://semver.org/) (the release version). Per-package detail lives in `packages/*/CHANGELOG.md` (generated by changesets); each release links its GitHub page, which carries the full narrative and install/upgrade commands. **Maintenance:** every user-facing change adds a changeset AND a one-line entry under **[Unreleased]**. At release time, [Unreleased] is retitled to the new version + date (matching the `chore(release)` commit), and a fresh [Unreleased] heading is started. ## [Unreleased] ## [0.31.0] — 2026-11-10 [Release](https://github.com/izzoa/polyrouter/releases/tag/v0.31.0) · [Compare](https://github.com/izzoa/polyrouter/compare/v0.30.0...v0.31.0) ### Added - **A subscription provider's Terms risk notice can be dismissed, once per provider.** The notice on the card gains a `×`; the server records the acknowledgment for that provider, so it stays gone on every browser, and the card shows nothing in its place. The add-provider dialog still states each plan's terms risk, and changing a provider's kind away from subscription and back brings the notice back. Notices about conditions that end once fixed keep no dismiss. New: `POST /api/providers/:id/terms-risk/acknowledge` and `termsRiskAcknowledgedAt` on the provider view; migration 0047 adds `provider.terms_risk_acknowledged_at`. ### Fixed - **Tall dialogs scroll at desktop width.** A dialog taller than the window, such as Edit provider on a subscription plan, ran off the bottom of the screen with no way to reach Save; dialogs are now held to the window's height and scroll inside themselves, as they already did on a phone. - **The dashboard no longer scrolls off a blank page.** Scrolling over the top bar on Overview or Requests could lift the whole dashboard and leave the bottom of the window blank, because screen-reader-only text in the request tables made the page taller than the window; that text now stays inside the scrolling content, so the page itself never scrolls. ### Upgrade notes - One migration runs on boot (`0047`): a nullable `provider.terms_risk_acknowledged_at` column. Every existing provider starts unacknowledged, so each subscription card keeps its Terms risk notice until you dismiss it. No data is rewritten and there is no new configuration. Pull `ghcr.io/izzoa/polyrouter:0.31.0` (or `:latest`) and restart. ## [0.30.0] — 2026-11-09 [Release](https://github.com/izzoa/polyrouter/releases/tag/v0.30.0) · [Compare](https://github.com/izzoa/polyrouter/compare/v0.29.0...v0.30.0) ### Added - **Subscription cards graph each plan's usage over its window, read about every 15 minutes even with no traffic.** Under the usage line, each limit window (5-hour, weekly, …) gets a small 0–100% chart over its span with the reset marked; a window with no reading yet shows an empty track labelled `no reading yet`, never a line at 0%. One instance at a time reads every subscription plan's usage endpoint about every 15 minutes, sharing the 5-minute cooldown with Refresh usage, and keeps a 35-day history. Claude Pro / Max and ChatGPT Plus / Pro gain usage endpoints and a Refresh usage button: they are undocumented account endpoints read with polyrouter's own User-Agent, and both providers restrict automated use of a consumer plan, so Edit gains **Check plan usage on a schedule** (on by default) to stop the unattended reads for one provider. Every usage endpoint now ships enabled; a broken one is withdrawn. Usage stays display only. New: `GET /api/providers/:id/plan-usage/history`, and `checkPlanUsage` on the provider view and `PATCH /api/providers/:id`. ### Changed - **Overview states the value served on subscription beside spend, and its headline strip ends level.** When the range holds subscription traffic, a line under the spend figure reads `$3.12 served on subscription, at API rates · excluded from spend` (Costs' words), and the Requests row states its share (`all on subscription plans` / `12 on subscription plans`); with none, neither appears. The other figures on Overview and Costs now sit as rows beside spend with their figures on one column, so the strip has no blank band; Costs keeps every figure and word. - **Kimi Code and Z.ai offer the identity-imitation switch when you connect them, not only in Edit afterwards.** The plan's detail in Add provider → Subscription shows the same switch, off, behind the same confirmation, and Connect sends your choice, so the new provider's first Test already uses it. `POST /api/providers/key-presets/connect` accepts an optional `imitateClientIdentity` (as the OAuth sign-in start does; refused for a plan that declares no identity), and the key-preset list gains `clientIdentityLabel`. No migration and no new configuration. ### Upgrade notes - One migration runs on boot (`0046`): a new table, `plan_usage_sample`, and a `provider.check_plan_usage` column (default on for every existing row). No data is rewritten, there is no backfill (the history starts at upgrade), and no new environment variable is required. - After the upgrade, polyrouter reads every subscription plan's usage endpoint about every 15 minutes, Claude and ChatGPT included. To keep a provider to reads you press, turn off **Check plan usage on a schedule** in its Edit dialog. ## [0.29.0] — 2026-11-09 [Release](https://github.com/izzoa/polyrouter/releases/tag/v0.29.0) · [Compare](https://github.com/izzoa/polyrouter/compare/v0.28.1...v0.29.0) ### Changed - **Add provider → Subscription lists every plan in one list with a detail pane, and the four coding-plan presets are now listed and connectable on every install.** The dialog widens to show Claude, ChatGPT, OpenCode Go, MiniMax M Plan, Kimi Code and Z.ai GLM Coding Plan side by side with the chosen plan's terms risk, region, key and identity note; Sign in and Connect act from that detail, and on a phone the list and the detail are two steps. The coding plans have not had a live run yet: a wrong pinned detail shows as a typed error at Test or Sync, never as a request to another host. The key-preset list gains `declaresIdentity`; no migration and no new configuration. ## [0.28.1] — 2026-11-07 [Release](https://github.com/izzoa/polyrouter/releases/tag/v0.28.1) · [Compare](https://github.com/izzoa/polyrouter/compare/v0.28.0...v0.28.1) ### Fixed - **Tool schemas with `null` keywords no longer make strict OpenAI-compatible providers reject the request.** A client that sends a null-valued standard keyword in a tool schema (Warp sends `"required": null` on its `finish` and `transfer_control_to_user` tools) got HTTP 400 from backends such as XiaomiMiMo, then fell back. The router now removes those nulls before forwarding, in every protocol and the batch path. Your request body is not modified, and if every tool's schema held only such nulls, the structural router no longer counts them as schema demand. ## [0.28.0] — 2026-11-07 [Release](https://github.com/izzoa/polyrouter/releases/tag/v0.28.0) · [Compare](https://github.com/izzoa/polyrouter/compare/v0.27.1...v0.28.0) ### Added - **Subscription cards can show how much of the plan is used, and a Refresh usage button for key plans.** One neutral line gives each window's used percent and reset time, says when a plan is billing extra usage (Claude usage credits, ChatGPT credits, the OpenCode Go balance), states a missing figure as `unknown` rather than `0%`, and ends with `as of