feat(desktop): add pricing override management UI - #4164
Conversation
|
Converting to draft and parking this, pending a product-direction decision. This PR implements the Pricing editor as the full built-in ∪ overrides table. After #2329, the maintainer re-evaluated the feature on #2218 and paused #2015, closing the dedicated full-catalog PR and suggesting a narrower overrides-only + catalog-picker surface (the built-in set is now ~1,400 models.dev entries), with the override schema to be settled in #2330. So the surface this PR builds is exactly the one under reconsideration — no amount of incremental polish resolves that, and it's a maintainer call, not a code one. The Runtime Host adapter (#2148) and Host projection are already merged; the remaining review items that are code-fixable have been addressed across the rounds (host-scoped writes, CAS against the loaded revision, reconciled-control reconnection that never replays, generation-race invalidation, conflict/uncertain UX, field-level validation, skeleton loading, disabled-reason tooltips, Heading role, reason propagation, tests). What remains open is genuinely gated on the direction: whether to keep a full effective table (needs pagination/virtualization + realistic fixtures) or pivot to overrides-only per #2330. Happy to reshape this into the overrides-only + picker surface once the #2330 contract is agreed, or to close this in favor of that. Parking rather than closing so the adapter-backed wiring and tests are easy to pick up. |
43ede3c to
78e5c2f
Compare
78e5c2f to
3bc7f25
Compare
Extract the Usage settings surface out of the frozen legacy renderer zone into `src/renderer/features/usage/`, matching main's feature-migration pattern (ports / services-context / ui + a settings-provided services boundary). Pure refactor — behavior identical to main (incl. the request-target Tooltip and the read-only pricing tab). Tracks apache#4425; unblocks apache#2015 / apache#4164. - features/usage: UsageSettingsView (surface + tabs + panels; owns the usage-stats state and loads via injected UsageServices), UsageServicesProvider + ports, feature-local table / metric-card / action-guard / optimistic-settings-draft, and a feature-local copy type. - settings/usage-settings-page.tsx reduced to a thin shim: assembles the services (loadUsageStats provided by settings-surface bound to the selected Host; updateUsageSettings delegates to onUpdateSettings, preserving its client-settings reconciliation), mounts the provider keyed by host:epoch, supplies SettingsPage chrome + locale copy + error description. - settings-surface.tsx: drops the usageStats state / reloadUsage / reload-effect; builds loadUsageStats bound to the selected Host and passes it + a host:epoch key + loadErrorTitle as props (no feature/platform import; no dependency-budget or bridge-path growth). - Feature files carry no legacy/preload import; copy stays in locales (passed as a prop). Regenerated the renderer-architecture ledger + astryx inventory. Refs apache#4425, apache#3439
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
Extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior matches main (incl. the request-target tooltip and the read-only pricing tab). - `features/usage/` owns the surface: narrow `UsageServices` ports (`loadUsageStats`, `updateUsageSettings` — `UsageSettings` in and out, never the whole `AppSettings`); a persistent `UsageFeatureScope` holding the single tagged `{ range, value }` snapshot + reload ticket + Host-generation invalidation + load-failure toast; and a disposable `UsageSettingsView`. - `settings/usage-settings-page.tsx` is a thin legacy shim (`UsageScopeMount` + `UsageSettingsPage`). `settings-surface.tsx` mounts the scope ABOVE its loading/error gate and passes a `host:epoch` `targetKey` as a prop; the scope clears and fences in place on a target change (no React `key`), so the snapshot survives a Skeleton/Banner state or a section change, and a Host change never remounts the rest of Settings. - Regenerate renderer-architecture.json and the astryx surface inventory. Extraction-only; the Desktop adapter/composition wiring and the editable pricing tab remain follow-ups. Refs apache#4425.
Extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the tooltip and the read-only pricing tab). - `features/usage/` owns the surface: narrow `UsageServices` ports (`loadUsageStats`, `updateUsageSettings` — `UsageSettings` in and out, never the whole `AppSettings`); a persistent `UsageFeatureScope` holding the single tagged `{ range, value }` snapshot + reload ticket + Host-generation invalidation + load-failure toast; and a disposable `UsageSettingsView`. - `settings/usage-settings-page.tsx` is a thin legacy shim (`UsageScopeMount` + `UsageSettingsPage`). `settings-surface.tsx` mounts the scope ABOVE its loading/error gate and passes a `host:epoch` `targetKey` as a prop; the scope clears and fences in place on a target change (no React `key`), so the snapshot survives a Skeleton/Banner state or a section change, and a Host change never remounts the rest of Settings. - Regenerate renderer-architecture.json and the astryx surface inventory. Extraction-only; the Desktop adapter/composition wiring and the editable pricing tab remain follow-ups. Refs apache#4425.
Extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior matches main (incl. the request-target tooltip and the read-only pricing tab). - `features/usage/` owns the surface: narrow `UsageServices` ports (`loadUsageStats`, `updateUsageSettings` — `UsageSettings` in and out, never the whole `AppSettings`); a persistent `UsageFeatureScope` holding the single tagged `{ range, value }` snapshot + reload ticket + Host-generation invalidation + load-failure toast; and a disposable `UsageSettingsView`. - `settings/usage-settings-page.tsx` is a thin legacy shim (`UsageScopeMount` + `UsageSettingsPage`). `settings-surface.tsx` mounts the scope ABOVE its loading/error gate and passes a `host:epoch` `targetKey` as a prop; the scope clears and fences in place on a target change (no React `key`). The Host-change handler also calls the scope's imperative `fenceTarget()` synchronously, so an in-flight old-Host load cannot land in the window before the re-render. - Regenerate renderer-architecture.json and the astryx surface inventory. Extraction-only; the Desktop adapter/composition wiring and the editable pricing tab remain follow-ups. Refs apache#4425. Generated-by: Claude Code
|
wait #4440 |
Extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the tooltip and the read-only pricing tab). - `features/usage/` owns the surface: narrow `UsageServices` ports (`loadUsageStats`, `updateUsageSettings` — `UsageSettings` in and out, never the whole `AppSettings`); a persistent `UsageFeatureScope` holding the single tagged `{ range, value }` snapshot + reload ticket + Host-generation invalidation + load-failure toast; and a disposable `UsageSettingsView`. - `settings/usage-settings-page.tsx` is a thin legacy shim (`UsageScopeMount` + `UsageSettingsPage`). `settings-surface.tsx` mounts the scope ABOVE its loading/error gate and passes a `host:epoch` `targetKey` as a prop; the scope clears and fences in place on a target change (no React `key`). The Host-change handler also calls the scope's imperative `fenceTarget()` synchronously, so an in-flight old-Host load cannot land in the window before the re-render. - Regenerate renderer-architecture.json and the astryx surface inventory. Extraction-only; the Desktop adapter/composition wiring and the editable pricing tab remain follow-ups. Refs apache#4425. Generated-by: Claude Code
Pure refactor: extract the Usage settings surface out of the frozen legacy renderer zone into a feature slice `src/renderer/features/usage/` (ports + services-context + ui), so net-new Usage work (the apache#2015/apache#4164 pricing tab) is no longer blocked by the apache#4088 AppShell-closure ratchet. Behavior is identical to main (incl. the request-target tooltip and the read-only pricing tab). - `settings/usage-settings-page.tsx` becomes a thin shim: assembles UsageServices, mounts the provider keyed by host:epoch, provides SettingsPage chrome + copy + describeError. `settings-surface.tsx` drops the usage-stats state/reload/effect and passes a host-bound loadUsageStats + providerKey as props (its dep count decreases). - Stats are stored tagged with their range; the derived view rejects a stale, late, or failed load whose range no longer matches the persisted one (covered by usage-settings-view.test.ts). Two deviations from the composition-feature pattern, both forced by the ratchet (documented in the feature README): the copy TYPE is feature-local (a shared file would be new closure debt), and there is no platform adapter / composition registration (frozen `settings-surface` cannot import the feature or platform, so the shim assembles the services). Refs apache#4425 Generated-by: Claude Code
11f2f21 to
08e7940
Compare
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
08e7940 to
e59acf4
Compare
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
e59acf4 to
6d23dbc
Compare
|
Reshaped this per your note on #2218 — it's now overrides-only with a catalog-backed picker, not the full effective table:
Rebased onto latest Happy to adjust further — in particular, if you'd prefer pricing overrides to live in #3129's unified model-facts layer (by extending its schema) rather than a dedicated surface, I'm glad to pivot to that instead. |
apache#4440) The renderer-architecture ratchet froze the legacy AppShell closure: no new file may enter it and no legacy file's dependency count may grow. Settings → Usage lives inside that closure, so net-new Usage work (most immediately the editable pricing tab of apache#2015 / apache#4164, which has to live in the Usage tabs) had nowhere to go. Usage moves to `src/renderer/features/usage/`, following the apache#3439 reference boundary. `ports.ts` narrows the port to `UsageSettings` / `UsageStats` instead of the whole `AppSettings`. `services-context.tsx` owns the state: one tagged `{ range, value }` snapshot, the reload ticket, unmount isolation, Host-generation invalidation, the failure toast, and an imperative `fenceTarget()`. `ui/usage-settings-view.tsx` is a disposable view that reads the snapshot and reloads on `[range, targetKey]`. `settings-surface.tsx` drops the `usageStats` state, `reloadUsage` and the prop threading; it mounts the scope above the loading gate so the snapshot survives a section change, and calls `fenceTarget()` synchronously on a Host change alongside the other Host-scoped resources. Behavior is unchanged. The DOM was compared tag by tag against the old page; the only differences are the two wrappers that moved into the shim and a type narrowing. No CSS, IPC or copy changed. One deviation from the composition-feature pattern, forced by the ratchet: there is no `platform/desktop` adapter and no composition wiring, because a frozen closure file cannot import the feature, and usage stats are scoped to the settings-selected Runtime Host, which the app-global composition root does not know about. `settings-surface` assembles the services and mounts the feature through a thin shim. When apache#4425's composition step lands, only that seam moves; the scope stays feature-owned. Two follow-ups are recorded in the feature README rather than done here: a `SettingsSurface` integration test covering the three lifecycle obligations (the current tests reconstruct the mount shape by hand and never load `settings-surface`), and de-duplicating the feature-local `action-guard` and `optimistic-settings-draft` copies, which are about 212 lines and the main source of the net addition. Refs apache#4425, apache#4582, apache#4088 Generated-by: Claude Code Generated-by: GLM-5.3-Flash (ZCode)
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
6d23dbc to
a82d286
Compare
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
a82d286 to
52dc16a
Compare
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
52dc16a to
4eecde2
Compare
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
0854f80 to
ac574c8
Compare
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
0be1e05 to
ed120fe
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Re-freeze review at head a41c1e02 (44 files, +3974/−312) against base 2310035a. This confirms my earlier review's code conclusion and closes the gate blocker from the previous head.
Prior blocker (architecture debt, 9 items) — resolved. The pricing feature now lives inside features/usage/ (controller, view model, editor, ports, services context); the only window.maka.settings.pricing access sits in platform/desktop/create-usage-pricing-services.ts, injected through composition/desktop-feature-services.tsx, so no new bridge path lands in the frozen legacy-AppShell closure files. The renderer-architecture and Astryx surface inventory checks pass in CI on this exact head.
Product direction — settled. Following the maintainer direction on the closed #2218, the surface is overrides-only: the table lists only custom rows, the built-in catalog is reachable through the Add dialog picker (never rendered as a full table), and duplicate validation is over existing overrides only (picking an unmodified built-in correctly creates a new override). The legacy usage:pricing:{list,put,reset} handlers and the read-only UsageStats.pricing projection are removed.
Correctness (key paths): mutations round-trip the loaded snapshot as the CAS base (the IPC boundary decodes it strictly: hostEpoch, connectionId, non-negative integer revision, canonical key order; a foreign base is rejected by the adapter's stale guard, a stale revision degrades to revision_conflict). Unknown dispatch outcomes go through the reconciled-control harness (the goal:arm pattern): wait for a replacement Host, reload fresh authority, compare the intended end state, and never replay the mutation; delete reconciliation preserves the distinct restore-builtin / become-unpriced / no-existing-override semantics. In-flight work is fenced by reload tickets plus a Host-generation epoch; refresh_failed drops the stale list and blocks writes; review_required enters an explicit-conflict state requiring a second save against the latest snapshot (an Add whose key appeared elsewhere converts to an Edit locked on that key). The view model is pure and validates rates (finite, ≥ 0) and keys (normalize / duplicate / locked) with an overrides-only duplicate rule.
Integration: the Pricing tab uses the controller-driven editor; the time-scoped range and summary toolbar are hidden on that tab (pricing is not time-scoped, per #2015 acceptance #2); the e2e budget adds settings-pricing.spec.ts with a real embedded Host round-trip and real Electron focus justification.
P3 (recorded): the Pricing tab changes from a read-only time-scoped panel to a non-time-scoped override editor — an intended behavior change under #2015/#2218, not a regression.
Not verified: I did not run the suites locally (CI on the exact head covers the affected desktop suite); the 526-line editor was checked at the handler level (save / reset / conflict all route through the controller), not pixel-level. A manual pass over Add → pick catalog → save → switch Host in a real Electron build is worth doing.
No P0–P2 findings. Refactor + feature; I am not approving — the merge decision belongs to humans. Epoch is unchanged (121 on base, head, and main), so there is no epoch-ordering constraint at merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
apache#4164) Add the editable Pricing Settings surface (apache#2015) to the migrated `features/usage` feature (apache#4425) as the Usage "pricing" tab, over the Host's CAS + reconciliation protocol. Per the maintainer direction on apache#2218 the surface is **overrides-only**: the table lists only the user's custom rows, and the built-in models.dev catalog (~1.4k rows) is reached only through the Add flow — never rendered as a table. (apache#3129's user-overridable model facts deliberately exclude pricing and ship no UI, so pricing remains its own dedicated surface.) Renderer (feature-owned, ratchet-clean): - Pricing controller, view-model, copy, and editor UI under `features/usage/`, rendered as the Usage "pricing" tab. Its services come from a dedicated `UsagePricingServices` port + provider + `platform/desktop` adapter wired through `composition/desktop-feature-services.tsx`, so the sole `window.maka.settings.pricing` bridge access stays in the platform zone. - Overrides-only table: `overrideRows` = the Host's `custom` entries (the Host collapses an overridden built-in into one custom row). The Usage range/summary toolbar is hidden on this tab (apache#2015 acceptance apache#2 — not time-scoped). - Add flow uses an Astryx `Typeahead` catalog picker over the Host's `builtin` entries (renders only the top matches, never the full list; a pick pre-fills the built-in price), with a manual-entry fallback for a model not in the catalog (local/new keys). Edit locks the key. Duplicate detection stays over the full built-in ∪ overrides union. Correctness (fixes found in review of the earlier full-table revision): - A committed mutation fences an in-flight reload (shared authority sequence), so a slow refresh can't overwrite the saved authority or clear a write-block. - A Host generation change resets all transient state (editor/draft/busy latches + action guard), so a dialog can't stick saving and an old-Host draft can't be saved onto the new authority. - An Add conflict whose key now exists elsewhere converts the Add into an Edit locked on that key, so the required second save upserts instead of being silently blocked by the duplicate check. - A saved-but-refresh-failed outcome clears the now-stale list (no speculative final list, per apache#2015) while retaining the draft until a successful refresh. Main / preload: the CAS pricing IPC (`usage:pricing:load` / `usage:pricing:mutate`) over `DesktopRuntimeHostClient`, a public `reconcilePricingMutation` for the reconciled-control path (reload + compare intent; never replay), and the declaration-only `desktop-pricing.d.ts` + main-only `desktop-pricing-decode.ts`. Core: remove the orphaned `UsageStats.pricing` field + its usage-stats projection. Tests: pricing view-model + a `PricingEditor` render suite (overrides-only table, catalog/manual Add, saved / refresh-failed / conflict / reconcile-unavailable / invalid-draft, and one regression per correctness fix above), plus the load/mutate IPC (base pass-through, malformed base, reconcile-no-replay). A Desktop E2E drives 设置 → 使用统计 → 定价配置: overrides-only (no 内置 rows), the absent range toolbar (apache#2), the catalog/manual Add UI, and editor focus restore (apache#11). Storybook stories (populated / empty / loading / load-failed) and the Astryx surface inventory regenerated. Refs apache#4164 apache#2015 apache#4425 apache#2218 Generated-by: Claude Code
Consolidate mutation reconciliation in the Runtime Host protocol, fence stale Host results synchronously, and retain uncertain mutation intent for explicit refresh reconciliation. Remove duplicate copy, bridge-derived types, stale styling, and unused controller surface while preserving the concurrency and CAS invariants required for correctness. Generated-by: Codex
Allow the manual-entry Storybook interaction to match the required-field suffix included in the input's accessible name. Generated-by: Codex
Surface conflict and uncertain-write recovery within the active pricing dialog, retain reconciliation intent without replay, and restore focus to a stable action when deleting an override removes its trigger. Add regression coverage for fresh CAS authority, draft preservation, modal recovery, and reset focus fallback; also align localized Host terminology. Generated-by: Codex
Re-pin the compatible pricing helper declaration to protocol epoch 136 and regenerate the Astryx surface inventory after rebasing onto current main. Generated-by: Codex
29f6b6b to
f4538fb
Compare
Summary
Adds an editable pricing override manager under Settings → Usage → Pricing for the Runtime Host selected in Settings.
Following the maintainer direction on #2218, this is an overrides-only surface:
Refs #2015 #2218 #4425 #4440
Verification
29f6b6ba5: run 34059080865.origin/main.Overrides-only table
Only custom overrides are listed; built-in rows remain available through the Add flow.
Catalog-backed Add dialog
Selecting a built-in model pre-fills its prices while keeping every value editable; manual entry remains available.
Review focus
saved_refresh_failedandreconciliation_unavailableretain the draft or reset intent and expose refresh inside the modal focus trap.AI use
Select exactly one:
Tool(s) and scope:
The affected commits include
Generated-by:trailers.Checklist
Does this PR entail a change in behavior?