dsh-subscriptions 0.1.2 → 0.1.4

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 (68) hide show
  1. package/README.md +92 -10
  2. package/lib/auth/rpc.d.ts +3 -1
  3. package/lib/auth/rpc.js +10 -1
  4. package/lib/client/ExternalUsageCards.d.ts +11 -1
  5. package/lib/client/ExternalUsageCards.js +18 -3
  6. package/lib/client/ProviderAccountManager.js +34 -41
  7. package/lib/client/ProviderModelEditor.js +5 -9
  8. package/lib/client/SpeedSelect.js +8 -7
  9. package/lib/client/SubscriptionUsageBadge.d.ts +19 -2
  10. package/lib/client/SubscriptionUsageBadge.js +65 -9
  11. package/lib/client/SubscriptionsSection.d.ts +22 -0
  12. package/lib/client/SubscriptionsSection.js +128 -38
  13. package/lib/client/UsageBadgeDisplaySetting.js +6 -10
  14. package/lib/client/UsageMeter.js +11 -6
  15. package/lib/client/host-contract.d.ts +1 -1
  16. package/lib/client/host-contract.js +22 -0
  17. package/lib/client/locales.d.ts +60 -0
  18. package/lib/client/locales.js +62 -2
  19. package/lib/client/provider-settings-styles.d.ts +3 -0
  20. package/lib/client/provider-settings-styles.js +206 -0
  21. package/lib/client/reset-action.d.ts +14 -0
  22. package/lib/client/reset-action.js +15 -0
  23. package/lib/client/reset-credits-view.d.ts +53 -0
  24. package/lib/client/reset-credits-view.js +103 -0
  25. package/lib/client/usage-badge-preferences.d.ts +7 -2
  26. package/lib/client/usage-badge-preferences.js +3 -2
  27. package/lib/client/usage-pace.d.ts +14 -1
  28. package/lib/client/usage-pace.js +14 -1
  29. package/lib/client.js +1036 -334
  30. package/lib/client.js.map +1 -1
  31. package/lib/index.d.ts +9 -0
  32. package/lib/index.js +1120 -602
  33. package/lib/providers/account-preferences.js +3 -2
  34. package/lib/providers/catalog-store.d.ts +3 -2
  35. package/lib/providers/catalog-store.js +57 -26
  36. package/lib/providers/claude.d.ts +22 -3
  37. package/lib/providers/claude.js +113 -15
  38. package/lib/providers/codex.d.ts +15 -2
  39. package/lib/providers/codex.js +72 -4
  40. package/lib/providers/common.d.ts +25 -0
  41. package/lib/providers/common.js +22 -18
  42. package/lib/providers/copilot.d.ts +2 -1
  43. package/lib/providers/copilot.js +10 -3
  44. package/lib/providers/grok.d.ts +2 -2
  45. package/lib/providers/grok.js +2 -2
  46. package/lib/providers/minimax-usage.d.ts +22 -1
  47. package/lib/providers/minimax-usage.js +37 -7
  48. package/lib/providers/pool-health.d.ts +2 -0
  49. package/lib/providers/pool-health.js +9 -0
  50. package/lib/providers/pool-usage.d.ts +13 -1
  51. package/lib/providers/pool-usage.js +39 -7
  52. package/lib/providers/pool.d.ts +7 -5
  53. package/lib/providers/pool.js +89 -12
  54. package/lib/providers/rate-limit.d.ts +0 -14
  55. package/lib/providers/rate-limit.js +0 -26
  56. package/lib/providers/replay.d.ts +4 -0
  57. package/lib/providers/replay.js +50 -0
  58. package/lib/providers/reset-redemption.d.ts +51 -0
  59. package/lib/providers/reset-redemption.js +102 -0
  60. package/lib/translate/anthropic.d.ts +21 -2
  61. package/lib/translate/anthropic.js +35 -11
  62. package/lib/translate/antigravity.js +13 -6
  63. package/lib/translate/chat-completions.js +1 -1
  64. package/lib/translate/resolved.d.ts +21 -0
  65. package/lib/translate/resolved.js +63 -3
  66. package/lib/translate/responses.js +8 -6
  67. package/lib/translate/sse.js +8 -0
  68. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -139,6 +139,35 @@ const en = {
139
139
  usageColorPaceHint: "Yellow when usage leads elapsed time by {allowance} points; red at 90% used takes priority. Saved in this browser; only colors change.",
140
140
  usageColorRemainingHint: "Red at 90% used; no yellow pace warnings. Reliable time markers remain visible. Saved in this browser.",
141
141
  usagePlan: "Plan: {plan}",
142
+ resetCreditsTitle: "Usage limit resets",
143
+ resetCreditsAvailable: "{count} available",
144
+ resetCreditsError: "Usage limit resets unavailable: {message}",
145
+ resetCreditsErrorFixed: "Usage limit resets are unavailable. Refresh usage to try again.",
146
+ resetCreditReadOnly: "Read only. This page cannot spend a Claude reset.",
147
+ resetCreditReadOnlyLabel: "Read only",
148
+ resetCreditAvailable: "Available",
149
+ resetCreditNotUsable: "Not yet usable",
150
+ resetCreditPaused: "Paused",
151
+ resetCreditExhausted: "Exhausted",
152
+ resetCreditExpired: "Expired",
153
+ resetCreditRemaining: "{left} of {total} left",
154
+ resetCreditLeft: "{left} left",
155
+ resetCreditRemainingUnknown: "Remaining count unavailable",
156
+ resetCreditCooldownUntil: "Usable after {date}",
157
+ resetUseButton: "Use…",
158
+ resetUseChecking: "Checking…",
159
+ resetUseGuard: "You can use this reset when weekly usage reaches at least 80%.",
160
+ resetUseRestart: "Reset details are missing. Restart DSH, reload this page, then refresh usage.",
161
+ resetUseRefresh: "Refresh usage to verify current limits and reset availability.",
162
+ resetUseReady: "Use this reset — the earliest-expiring credit. Confirmation required.",
163
+ resetUseConfirmTitle: "Confirm full reset",
164
+ resetUseWarning: "This consumes one credit and resets weekly + 5-hour limits. This interface cannot undo it. Confirmation expires in 60 seconds.",
165
+ resetUseAcknowledge: "I understand that one credit will be consumed.",
166
+ resetUseConfirm: "Consume 1 reset",
167
+ resetUseSuccess: "Reset confirmed. Usage refreshed.",
168
+ resetCreditFull: "Full reset",
169
+ resetCreditExpires: "Expires {date}",
170
+ resetCreditExpiryUnknown: "Expiry date unavailable",
142
171
  externalUsageTitle: "Built-in provider usage",
143
172
  externalUsageConnected: "API key configured",
144
173
  externalUsageNotConfigured: "Not configured",
@@ -190,8 +219,9 @@ const en = {
190
219
  usageBadgeTitle: "Provider usage",
191
220
  usageBadgeDisplay: "Status-bar quota display",
192
221
  usageBadgeDisplayRecent: "Current / most recent provider with usage",
222
+ usageBadgeDisplayAlways: "Always show one provider, rotating through the rest",
193
223
  usageBadgeDisplayHidden: "Hidden",
194
- usageBadgeDisplayHint: "Saved automatically in this browser. One provider is shown at a time. Models without a quota source keep the most recent provider selected in this view, or show nothing. Hiding does not affect usage details here.",
224
+ usageBadgeDisplayHint: "Saved automatically in this browser. One provider is shown at a time. Models without a quota source keep the most recent provider selected in this view, or show nothing. Always show instead rotates through the providers with usage every 10 seconds while the current model has no quota source. Hiding does not affect usage details here.",
195
225
  usageBadgeDisplaySaveFailed: "Could not save the display preference. Check browser storage permissions.",
196
226
  usageBadgeCurrent: "current",
197
227
  usageBadgeModelCount: "{count} model quotas",
@@ -310,6 +340,35 @@ const zh = {
310
340
  usageColorPaceHint: "用量领先时间进度 {allowance} 个百分点时变黄;已用达到 90% 时优先变红。自动保存到当前浏览器,仅改变配色。",
311
341
  usageColorRemainingHint: "已用达到 90% 时变红,不显示黄色节奏警告。可靠的时间标记仍保留,自动保存到当前浏览器。",
312
342
  usagePlan: "计划:{plan}",
343
+ resetCreditsTitle: "用量限制重置",
344
+ resetCreditsAvailable: "可用 {count} 次",
345
+ resetCreditsError: "用量限制重置查询失败:{message}",
346
+ resetCreditsErrorFixed: "用量限制重置暂不可用,请刷新用量后重试。",
347
+ resetCreditReadOnly: "只读。此页面不能消耗 Claude 的重置额度。",
348
+ resetCreditReadOnlyLabel: "只读",
349
+ resetCreditAvailable: "可用",
350
+ resetCreditNotUsable: "暂不可用",
351
+ resetCreditPaused: "已暂停",
352
+ resetCreditExhausted: "已用尽",
353
+ resetCreditExpired: "已过期",
354
+ resetCreditRemaining: "剩余 {left} / {total}",
355
+ resetCreditLeft: "剩余 {left}",
356
+ resetCreditRemainingUnknown: "剩余次数不可用",
357
+ resetCreditCooldownUntil: "{date} 后可用",
358
+ resetUseButton: "使用…",
359
+ resetUseChecking: "正在检查…",
360
+ resetUseGuard: "每周用量达到 80% 后才可使用此重置。",
361
+ resetUseRestart: "缺少重置详情。请重启 DSH、重新加载页面并刷新用量。",
362
+ resetUseRefresh: "请刷新用量以验证当前限制和重置可用性。",
363
+ resetUseReady: "使用此最早到期的重置额度,操作前需要确认。",
364
+ resetUseConfirmTitle: "确认完整重置",
365
+ resetUseWarning: "将消耗一次额度并重置每周和 5 小时限制,无法撤销。确认将在 60 秒后过期。",
366
+ resetUseAcknowledge: "我理解将消耗一次额度。",
367
+ resetUseConfirm: "消耗一次重置",
368
+ resetUseSuccess: "重置已确认,用量已刷新。",
369
+ resetCreditFull: "完整重置",
370
+ resetCreditExpires: "{date} 到期",
371
+ resetCreditExpiryUnknown: "到期时间不可用",
313
372
  externalUsageTitle: "内置供应商用量",
314
373
  externalUsageConnected: "已配置 API 密钥",
315
374
  externalUsageNotConfigured: "未配置",
@@ -361,8 +420,9 @@ const zh = {
361
420
  usageBadgeTitle: "服务商用量",
362
421
  usageBadgeDisplay: "状态栏额度显示",
363
422
  usageBadgeDisplayRecent: "仅显示当前/最近使用的用量服务商",
423
+ usageBadgeDisplayAlways: "始终显示一家,其余服务商轮播",
364
424
  usageBadgeDisplayHidden: "不显示",
365
- usageBadgeDisplayHint: "自动保存到当前浏览器。状态栏一次显示一家服务商;没有额度来源的模型沿用本视图最近选择的服务商,没有记录则隐藏。关闭不影响本页的额度详情。",
425
+ usageBadgeDisplayHint: "自动保存到当前浏览器。状态栏一次显示一家服务商;没有额度来源的模型沿用本视图最近选择的服务商,没有记录则隐藏。选择“始终显示”时,当前模型没有额度来源,就每 10 秒轮播一家有额度的服务商。关闭不影响本页的额度详情。",
366
426
  usageBadgeDisplaySaveFailed: "显示设置保存失败,请检查浏览器存储权限。",
367
427
  usageBadgeCurrent: "当前",
368
428
  usageBadgeModelCount: "{count} 个模型配额",
@@ -377,6 +437,18 @@ const zh = {
377
437
  commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用"
378
438
  };
379
439
 
440
+ //#endregion
441
+ //#region src/client/reset-action.ts
442
+ /** Explain client-side gating; the server independently revalidates before spending. */
443
+ function resetActionBlock(usage, now = Date.now()) {
444
+ const weekly = usage.windows?.find((w) => w.kind === "weekly" && w.scope === void 0);
445
+ if (!weekly || !Number.isFinite(weekly.usedPercent)) return "resetUseRefresh";
446
+ if (weekly.usedPercent < 80) return "resetUseGuard";
447
+ if (weekly.resetsAt === void 0 || weekly.resetsAt <= now || usage.resetCreditsError) return "resetUseRefresh";
448
+ if (!usage.resetCredits?.some((c) => c.id)) return "resetUseRestart";
449
+ if (!usage.resetCredits.some((c) => c.id && (c.expiresAt === void 0 || c.expiresAt > now))) return "resetUseRefresh";
450
+ }
451
+
380
452
  //#endregion
381
453
  //#region src/client/subscriptions-rpc.ts
382
454
  /**
@@ -449,16 +521,7 @@ function mergeAccountChanges(latest, changes) {
449
521
 
450
522
  //#endregion
451
523
  //#region src/client/ProviderModelEditor.tsx
452
- const border$1 = "1px solid var(--dsw-alias-border-l2, #ddd)";
453
- const control$1 = {
454
- font: "inherit",
455
- color: "inherit",
456
- background: "transparent",
457
- border: border$1,
458
- borderRadius: 8,
459
- padding: "6px 10px",
460
- minWidth: 0
461
- };
524
+ const border$1 = "0.5px solid var(--dsw-alias-border-l2, #ddd)";
462
525
  const actions$1 = {
463
526
  display: "flex",
464
527
  flexWrap: "wrap",
@@ -567,10 +630,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
567
630
  paddingTop: 12
568
631
  },
569
632
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
570
- style: {
571
- margin: 0,
572
- fontSize: 15
573
- },
633
+ style: { margin: 0 },
574
634
  children: t("modelsEdit")
575
635
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
576
636
  style: {
@@ -580,22 +640,18 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
580
640
  },
581
641
  children: [
582
642
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
583
- style: { margin: 0 },
643
+ className: "dsh-subscription-hint",
584
644
  children: t(provider === "cursor-subscription" ? "cursorManageHint" : "modelsHint")
585
645
  }),
586
646
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
587
647
  role: "alert",
588
- style: {
589
- margin: 0,
590
- color: "var(--dsw-alias-state-error-primary, #b42318)"
591
- },
592
648
  children: error
593
649
  }),
594
650
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
595
651
  style: actions$1,
596
652
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
597
653
  type: "button",
598
- style: control$1,
654
+ className: "dsh-subscription-quiet",
599
655
  disabled: busy || disabled || dirty,
600
656
  onClick: () => {
601
657
  load(true);
@@ -635,10 +691,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
635
691
  style: actions$1,
636
692
  children: [
637
693
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
638
- style: {
639
- ...control$1,
640
- flex: "1 1 180px"
641
- },
694
+ style: { flex: "1 1 180px" },
642
695
  value: query,
643
696
  onChange: (event) => setQuery(event.target.value),
644
697
  placeholder: t("modelDefaultsFilterPlaceholder"),
@@ -646,7 +699,6 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
646
699
  }),
647
700
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
648
701
  type: "button",
649
- style: control$1,
650
702
  onClick: () => edit({
651
703
  ...draft,
652
704
  visibleModels: allModels.map((model) => model.id)
@@ -655,7 +707,6 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
655
707
  }),
656
708
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
657
709
  type: "button",
658
- style: control$1,
659
710
  onClick: () => edit({
660
711
  ...draft,
661
712
  visibleModels: []
@@ -703,7 +754,6 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
703
754
  children: [(model.efforts?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
704
755
  style: actions$1,
705
756
  children: [t("modelDefaultsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
706
- style: control$1,
707
757
  "aria-label": `${model.name} ${t("modelDefaultsTitle")}`,
708
758
  value: efforts[model.id] ?? "",
709
759
  onChange: (event) => {
@@ -739,10 +789,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
739
789
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
740
790
  style: actions$1,
741
791
  children: [t("modelsContext"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
742
- style: {
743
- ...control$1,
744
- width: 140
745
- },
792
+ style: { width: 140 },
746
793
  inputMode: "numeric",
747
794
  value: contexts[model.id] ?? "",
748
795
  "aria-label": `${model.name} ${t("modelsContext")}`,
@@ -796,28 +843,218 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
796
843
  });
797
844
  });
798
845
 
846
+ //#endregion
847
+ //#region src/client/provider-settings-styles.ts
848
+ /**
849
+ * The host loads one JS client bundle, so keep this small, scoped stylesheet
850
+ * with the dialog instead of relying on a separate CSS asset. Geometry and
851
+ * theme tokens follow the host's settings-form and ui-primitives controls.
852
+ *
853
+ * `color-scheme` is left to the host deliberately. Its boot script sets the
854
+ * property on `:root`, and it inherits, so native controls inside these panels
855
+ * follow the theme. Upstream also pins `color-scheme: light` here and flips it
856
+ * back under `body[data-ds-dark-theme]`; without that flip our scoped rules are
857
+ * more specific than `:root` and would force every native control light inside
858
+ * a dark theme.
859
+ */
860
+ const formScope = ":is(.dsh-subscription-manager, .dsh-subscriptions-settings)";
861
+ const providerSettingsCss = `
862
+ .dsh-subscription-manager {
863
+ box-sizing: border-box;
864
+ padding: 0;
865
+ border: 0;
866
+ border-radius: var(--dsw-radius-panel, 16px);
867
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));
868
+ color: var(--dsw-alias-label-primary);
869
+ box-shadow: var(--dsw-elevation-prominent, 0 20px 70px #0004);
870
+ font-family: inherit;
871
+ font-size: 13px;
872
+ line-height: 1.5;
873
+ }
874
+ .dsh-subscriptions-settings {
875
+ font-family: inherit;
876
+ font-size: 13px;
877
+ line-height: 1.5;
878
+ }
879
+ .dsh-subscription-manager::backdrop {
880
+ background: var(--dsw-alias-bg-mask-1, #0006);
881
+ backdrop-filter: var(--dsw-mask-blur, none);
882
+ }
883
+ ${formScope} h2 {
884
+ font-size: 16px;
885
+ line-height: 24px;
886
+ font-weight: 500;
887
+ }
888
+ ${formScope} h3 {
889
+ font-size: 14px;
890
+ line-height: 22px;
891
+ font-weight: 500;
892
+ }
893
+ ${formScope} strong,
894
+ ${formScope} .dsh-subscription-label {
895
+ font-size: 13px;
896
+ line-height: 1.5;
897
+ font-weight: 500;
898
+ }
899
+ ${formScope} legend {
900
+ font-size: 14px;
901
+ line-height: 22px;
902
+ font-weight: 500;
903
+ }
904
+ ${formScope} small,
905
+ ${formScope} .dsh-subscription-hint,
906
+ ${formScope} [role="status"] {
907
+ margin: 0;
908
+ font-size: 12px;
909
+ line-height: 1.5;
910
+ color: var(--dsw-alias-label-tertiary);
911
+ }
912
+ ${formScope} [role="alert"] {
913
+ margin: 0;
914
+ font-size: 12px;
915
+ line-height: 1.5;
916
+ color: var(--dsw-alias-state-error-primary, #b42318);
917
+ }
918
+ ${formScope} input:not([type="checkbox"]),
919
+ ${formScope} select {
920
+ box-sizing: border-box;
921
+ min-width: 0;
922
+ height: 34px;
923
+ padding: 0 12px;
924
+ border: 0.5px solid var(--dsw-alias-border-l4, var(--dsw-alias-border-l2));
925
+ border-radius: var(--dsw-radius-md, 8px);
926
+ background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1));
927
+ color: var(--dsw-alias-label-primary);
928
+ font: inherit;
929
+ font-size: 13px;
930
+ font-weight: 400;
931
+ line-height: 1.5;
932
+ }
933
+ ${formScope} input::placeholder {
934
+ color: var(--dsw-alias-label-tertiary);
935
+ opacity: 1;
936
+ }
937
+ ${formScope} input:not([type="checkbox"]):focus-visible,
938
+ ${formScope} select:focus-visible {
939
+ outline: none;
940
+ border-color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary));
941
+ }
942
+ ${formScope} input:not([type="checkbox"]):disabled,
943
+ ${formScope} select:disabled {
944
+ color: var(--dsw-alias-label-tertiary);
945
+ cursor: default;
946
+ }
947
+ ${formScope} input[aria-invalid="true"] {
948
+ border-color: var(--dsw-alias-state-error-primary, #b42318);
949
+ }
950
+ ${formScope} option {
951
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));
952
+ color: var(--dsw-alias-label-primary);
953
+ }
954
+ ${formScope} label:has(> input[type="checkbox"]) {
955
+ display: flex;
956
+ align-items: flex-start;
957
+ gap: 6px;
958
+ font-size: 14px;
959
+ line-height: 20px;
960
+ cursor: pointer;
961
+ }
962
+ ${formScope} input[type="checkbox"] {
963
+ flex: 0 0 auto;
964
+ width: 16px;
965
+ height: 16px;
966
+ margin: 2px 0;
967
+ accent-color: var(--dsw-alias-brand-primary);
968
+ cursor: inherit;
969
+ }
970
+ ${formScope} label:has(> input[type="checkbox"]:disabled) {
971
+ cursor: default;
972
+ opacity: 0.5;
973
+ }
974
+ ${formScope} button {
975
+ box-sizing: border-box;
976
+ display: inline-flex;
977
+ align-items: center;
978
+ justify-content: center;
979
+ flex-shrink: 0;
980
+ min-height: 28px;
981
+ padding: 0 10px;
982
+ border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
983
+ border-radius: var(--dsw-radius-sm, 6px);
984
+ background: transparent;
985
+ color: var(--dsw-alias-label-primary);
986
+ font: inherit;
987
+ font-size: 12px;
988
+ line-height: 18px;
989
+ cursor: pointer;
990
+ }
991
+ ${formScope} button:hover:not(:disabled) {
992
+ background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-3));
993
+ }
994
+ ${formScope} button:active:not(:disabled) {
995
+ background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-3));
996
+ }
997
+ ${formScope} button:disabled {
998
+ opacity: 0.4;
999
+ cursor: default;
1000
+ }
1001
+ ${formScope} button.dsh-subscription-quiet {
1002
+ border-color: transparent;
1003
+ color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));
1004
+ }
1005
+ ${formScope} :is(footer, .dsh-subscription-dialog-actions) button {
1006
+ min-height: 34px;
1007
+ padding: 0 14px;
1008
+ border-radius: var(--dsw-radius-md, 8px);
1009
+ font-size: 13px;
1010
+ line-height: 1.5;
1011
+ }
1012
+ ${formScope} button.dsh-subscription-primary {
1013
+ border-color: transparent;
1014
+ background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-label-primary));
1015
+ color: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-1));
1016
+ font-weight: 500;
1017
+ }
1018
+ ${formScope} button.dsh-subscription-primary:hover:not(:disabled) {
1019
+ background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-label-secondary));
1020
+ }
1021
+ ${formScope} button:focus-visible,
1022
+ ${formScope} input[type="checkbox"]:focus-visible,
1023
+ ${formScope} summary:focus-visible {
1024
+ outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
1025
+ outline-offset: 2px;
1026
+ }
1027
+ ${formScope} summary {
1028
+ border-radius: var(--dsw-radius-sm, 6px);
1029
+ }
1030
+ ${formScope} summary:hover {
1031
+ color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));
1032
+ }
1033
+ `;
1034
+ /** Composer controls retain their own geometry, separate from settings forms. */
1035
+ const subscriptionChromeCss = `
1036
+ .dsh-subscription-usage-pill { corner-shape: round; }
1037
+ .dsh-subscription-usage-panel {
1038
+ --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
1039
+ backdrop-filter: var(--dsw-menu-backdrop-filter, none);
1040
+ }
1041
+ .dsh-subscription-speed button:hover:not(:disabled) {
1042
+ --dsh-subscription-speed-hover: var(--dsw-alias-interactive-bg-hover);
1043
+ }
1044
+ .dsh-subscription-speed button:active:not(:disabled) {
1045
+ --dsh-subscription-speed-hover: var(--dsw-alias-interactive-bg-active);
1046
+ }
1047
+ .dsh-subscription-speed button:disabled { opacity: 0.4; cursor: default; }
1048
+ .dsh-subscription-speed button:focus-visible,
1049
+ .dsh-subscription-usage-pill:focus-visible {
1050
+ outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
1051
+ outline-offset: 2px;
1052
+ }
1053
+ `;
1054
+
799
1055
  //#endregion
800
1056
  //#region src/client/ProviderAccountManager.tsx
801
- const border = "1px solid var(--dsw-alias-border-l2)";
802
- const control = {
803
- font: "inherit",
804
- color: "inherit",
805
- background: "var(--dsw-alias-bg-layer-1)",
806
- border,
807
- borderRadius: 8,
808
- padding: "7px 12px",
809
- minWidth: 0
810
- };
811
- const button = {
812
- ...control,
813
- cursor: "pointer"
814
- };
815
- const hint = {
816
- margin: 0,
817
- fontSize: 12,
818
- lineHeight: 1.6,
819
- color: "var(--dsw-alias-label-tertiary)"
820
- };
1057
+ const border = "0.5px solid var(--dsw-alias-border-l2)";
821
1058
  const stack = {
822
1059
  display: "grid",
823
1060
  gap: 12,
@@ -922,7 +1159,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
922
1159
  if (alive.current) setSaving(false);
923
1160
  }
924
1161
  }
925
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", {
1162
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
1163
+ className: "dsh-subscription-manager",
926
1164
  ref: dialog,
927
1165
  "aria-labelledby": title,
928
1166
  "aria-describedby": description,
@@ -934,16 +1172,9 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
934
1172
  width: 620,
935
1173
  maxWidth: "calc(100vw - 32px)",
936
1174
  maxHeight: "calc(100dvh - 32px)",
937
- boxSizing: "border-box",
938
- padding: 0,
939
- border,
940
- borderRadius: 16,
941
- color: "var(--dsw-alias-label-primary)",
942
- background: "var(--dsw-alias-bg-layer-1)",
943
- boxShadow: "0 20px 70px #0004",
944
1175
  overflow: "auto"
945
1176
  },
946
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1177
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: providerSettingsCss }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
947
1178
  style: {
948
1179
  ...stack,
949
1180
  padding: 20
@@ -956,15 +1187,11 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
956
1187
  },
957
1188
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
958
1189
  id: title,
959
- style: {
960
- margin: 0,
961
- fontSize: 18
962
- },
1190
+ style: { margin: 0 },
963
1191
  children: t("accountsTitle", { provider: name })
964
1192
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
965
1193
  type: "button",
966
1194
  autoFocus: true,
967
- style: button,
968
1195
  disabled: saving,
969
1196
  onClick: onClose,
970
1197
  children: t("imageClose")
@@ -972,25 +1199,20 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
972
1199
  }),
973
1200
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
974
1201
  id: description,
975
- style: hint,
1202
+ className: "dsh-subscription-hint",
976
1203
  children: t(provider === "cursor-subscription" ? "cursorAccountsHint" : "accountsHint")
977
1204
  }),
978
1205
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
979
1206
  role: "alert",
980
- style: {
981
- ...hint,
982
- color: "var(--dsw-alias-state-error-primary)"
983
- },
984
1207
  children: error
985
1208
  }),
986
1209
  loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
987
1210
  role: "status",
988
- style: hint,
1211
+ className: "dsh-subscription-hint",
989
1212
  children: t("accountsLoading")
990
1213
  }),
991
1214
  !loading && !catalog && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
992
1215
  type: "button",
993
- style: button,
994
1216
  onClick: () => setAttempt((value) => value + 1),
995
1217
  children: t("modelDefaultsRetry")
996
1218
  }),
@@ -1004,24 +1226,20 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1004
1226
  },
1005
1227
  children: [
1006
1228
  catalog.accounts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1007
- style: hint,
1229
+ className: "dsh-subscription-hint",
1008
1230
  children: t(provider === "cursor-subscription" ? "cursorManageSignIn" : "accountsEmpty")
1009
1231
  }),
1010
1232
  provider === "cursor-subscription" && catalog.accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
1011
1233
  style: {
1012
1234
  border,
1013
- borderRadius: 12,
1235
+ borderRadius: "var(--dsw-radius-lg, 12px)",
1014
1236
  padding: 14
1015
1237
  },
1016
1238
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
1017
- style: {
1018
- padding: "0 6px",
1019
- fontWeight: 600,
1020
- fontSize: 14
1021
- },
1239
+ style: { padding: "0 6px" },
1022
1240
  children: name
1023
1241
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1024
- style: hint,
1242
+ className: "dsh-subscription-hint",
1025
1243
  children: t("cursorConnected")
1026
1244
  })]
1027
1245
  }),
@@ -1033,7 +1251,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1033
1251
  style: {
1034
1252
  ...stack,
1035
1253
  border,
1036
- borderRadius: 12,
1254
+ borderRadius: "var(--dsw-radius-lg, 12px)",
1037
1255
  padding: 14,
1038
1256
  margin: 0
1039
1257
  },
@@ -1041,15 +1259,13 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1041
1259
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
1042
1260
  style: {
1043
1261
  padding: "0 6px",
1044
- fontWeight: 600,
1045
- fontSize: 14,
1046
1262
  overflowWrap: "anywhere",
1047
1263
  maxWidth: "100%"
1048
1264
  },
1049
1265
  children: account.label
1050
1266
  }),
1051
1267
  account.unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1052
- style: hint,
1268
+ className: "dsh-subscription-hint",
1053
1269
  children: t("accountsCatalogUnavailable")
1054
1270
  }),
1055
1271
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -1058,10 +1274,9 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1058
1274
  gap: 6
1059
1275
  },
1060
1276
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1061
- style: { fontSize: 12 },
1277
+ className: "dsh-subscription-label",
1062
1278
  children: t("accountsAlias")
1063
1279
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1064
- style: control,
1065
1280
  value: preferences.alias ?? "",
1066
1281
  placeholder: account.label,
1067
1282
  onChange: (event) => {
@@ -1097,7 +1312,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1097
1312
  t("accountsIndependent")
1098
1313
  ] }),
1099
1314
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1100
- style: hint,
1315
+ className: "dsh-subscription-hint",
1101
1316
  children: t("accountsIndependentHint")
1102
1317
  }),
1103
1318
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
@@ -1117,7 +1332,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1117
1332
  },
1118
1333
  children: [
1119
1334
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1120
- style: hint,
1335
+ className: "dsh-subscription-hint",
1121
1336
  children: t("accountsModelsHint")
1122
1337
  }),
1123
1338
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
@@ -1138,7 +1353,6 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1138
1353
  style: actions,
1139
1354
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1140
1355
  type: "button",
1141
- style: button,
1142
1356
  onClick: () => edit(account.key, {
1143
1357
  ...preferences,
1144
1358
  poolModels: models.map((model) => model.id)
@@ -1146,7 +1360,6 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1146
1360
  children: t("modelsSelectAll")
1147
1361
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1148
1362
  type: "button",
1149
- style: button,
1150
1363
  onClick: () => edit(account.key, {
1151
1364
  ...preferences,
1152
1365
  poolModels: []
@@ -1162,10 +1375,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1162
1375
  overflowY: "auto"
1163
1376
  },
1164
1377
  children: [models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1165
- style: {
1166
- fontSize: 13,
1167
- overflowWrap: "anywhere"
1168
- },
1378
+ style: { overflowWrap: "anywhere" },
1169
1379
  children: [
1170
1380
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1171
1381
  type: "checkbox",
@@ -1185,7 +1395,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1185
1395
  model.unavailable && ` (${t("modelsUnavailable")})`
1186
1396
  ]
1187
1397
  }, model.id)), models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1188
- style: hint,
1398
+ className: "dsh-subscription-hint",
1189
1399
  children: t("accountsNoModels")
1190
1400
  })]
1191
1401
  })
@@ -1209,17 +1419,13 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1209
1419
  ...stack,
1210
1420
  gap: 8,
1211
1421
  borderTop: border,
1212
- padding: "14px 0 0",
1422
+ padding: "14px 0 8px",
1213
1423
  position: "sticky",
1214
1424
  bottom: 0,
1215
- background: "var(--dsw-alias-bg-layer-1)"
1425
+ background: "var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))"
1216
1426
  },
1217
1427
  children: [saveError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1218
1428
  role: "alert",
1219
- style: {
1220
- ...hint,
1221
- color: "var(--dsw-alias-state-error-primary)"
1222
- },
1223
1429
  children: saveError
1224
1430
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1225
1431
  style: {
@@ -1228,16 +1434,12 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1228
1434
  },
1229
1435
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1230
1436
  type: "button",
1231
- style: button,
1232
1437
  disabled: saving,
1233
1438
  onClick: onClose,
1234
1439
  children: t("cancel")
1235
1440
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1236
1441
  type: "button",
1237
- style: {
1238
- ...button,
1239
- fontWeight: 600
1240
- },
1442
+ className: "dsh-subscription-primary",
1241
1443
  disabled: loading || saving || !Object.keys(changes).length && !modelsDirty,
1242
1444
  onClick: () => {
1243
1445
  save();
@@ -1247,7 +1449,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
1247
1449
  })]
1248
1450
  })
1249
1451
  ]
1250
- })
1452
+ })]
1251
1453
  });
1252
1454
  }
1253
1455
 
@@ -1402,7 +1604,8 @@ const USAGE_BADGE_REFRESH_EVENT = "dsh:subscriptions:usage-refresh";
1402
1604
  function readUsageBadgeMode() {
1403
1605
  if (typeof window === "undefined") return "recent";
1404
1606
  try {
1405
- return window.localStorage.getItem(MODE_KEY) === "hidden" ? "hidden" : "recent";
1607
+ const value = window.localStorage.getItem(MODE_KEY);
1608
+ return value === "hidden" || value === "always" ? value : "recent";
1406
1609
  } catch (error) {
1407
1610
  if (!(error instanceof DOMException) || error.name !== "SecurityError") throw error;
1408
1611
  return "recent";
@@ -1410,7 +1613,7 @@ function readUsageBadgeMode() {
1410
1613
  }
1411
1614
  /** Save first, then notify; the settings control reports a rejected write. */
1412
1615
  function setUsageBadgeMode(mode) {
1413
- if (mode !== "recent" && mode !== "hidden") throw new TypeError("Invalid subscription usage display mode");
1616
+ if (mode !== "recent" && mode !== "always" && mode !== "hidden") throw new TypeError("Invalid subscription usage display mode");
1414
1617
  window.localStorage.setItem(MODE_KEY, mode);
1415
1618
  window.dispatchEvent(new Event(MODE_EVENT));
1416
1619
  }
@@ -1466,10 +1669,23 @@ function resetCountdownParts(ms) {
1466
1669
  }
1467
1670
  ].filter((part) => part.count > 0).slice(0, 2);
1468
1671
  }
1672
+ /**
1673
+ * Whether the reported percentage still describes the window now. Only a fresh
1674
+ * reading may be compared with the current time (pace warnings); an old one
1675
+ * keeps its absolute color. This does not decide whether the time cursor is
1676
+ * drawn: where the window stands in time never depends on the reading.
1677
+ */
1469
1678
  function isUsageFresh(window$1, observation, now = Date.now()) {
1470
1679
  return validUsage(window$1.usedPercent) && observation.stale !== true && observation.observedAt !== void 0 && Number.isFinite(observation.observedAt) && observation.observedAt <= now && now - observation.observedAt <= USAGE_FRESHNESS_MS && (window$1.resetsAt === void 0 || Number.isFinite(window$1.resetsAt) && window$1.resetsAt > now);
1471
1680
  }
1472
- /** Only explicit intervals or provider-verified fixed durations support linear pace. */
1681
+ /**
1682
+ * How far through its window the clock is, from the window's own timing alone.
1683
+ * It ignores the percentage and how recently it was read, so an unused,
1684
+ * stale, aged or unobserved reading keeps its cursor whenever the timing
1685
+ * places it. Returns undefined when the timing cannot: only an explicit
1686
+ * interval or a provider-verified fixed duration supports linear pace, and no
1687
+ * start is invented from a window's kind or label.
1688
+ */
1473
1689
  function elapsedPercent(window$1, now = Date.now()) {
1474
1690
  const end = window$1.resetsAt;
1475
1691
  if (end === void 0 || !Number.isFinite(end) || end <= now) return void 0;
@@ -1548,17 +1764,18 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
1548
1764
  observedAt,
1549
1765
  stale
1550
1766
  }, current);
1551
- const elapsed = fresh ? elapsedPercent(quota, current) : void 0;
1552
- const state = usageColorState(quota.usedPercent, elapsed, preset, fresh);
1553
- const lead = elapsed === void 0 ? 0 : Math.round(used - elapsed);
1767
+ const elapsed = elapsedPercent(quota, current);
1768
+ const pace = fresh ? elapsed : void 0;
1769
+ const state = usageColorState(quota.usedPercent, pace, preset, fresh);
1770
+ const lead = pace === void 0 ? 0 : Math.round(used - pace);
1554
1771
  const label = [
1555
1772
  valid ? t("usageMeterUsed", {
1556
1773
  used: Math.round(used),
1557
1774
  remaining: Math.round(100 - used)
1558
1775
  }) : t("usageMeterInvalid"),
1559
- !valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") : elapsed === void 0 && preset !== "remaining" ? t("usageMeterUnknownTime") : state === "yellow" ? t("usageMeterAhead") : t("usageMeterNoWarning"),
1776
+ !valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") : pace === void 0 && preset !== "remaining" ? t("usageMeterUnknownTime") : state === "yellow" ? t("usageMeterAhead") : t("usageMeterNoWarning"),
1560
1777
  elapsed === void 0 ? "" : t("usageMeterPace", { elapsed: Math.round(elapsed) }),
1561
- elapsed === void 0 ? "" : lead > 0 ? t("usageMeterPaceAhead", { points: lead }) : lead < 0 ? t("usageMeterPaceBehind", { points: -lead }) : t("usageMeterPaceEven"),
1778
+ pace === void 0 ? "" : lead > 0 ? t("usageMeterPaceAhead", { points: lead }) : lead < 0 ? t("usageMeterPaceBehind", { points: -lead }) : t("usageMeterPaceEven"),
1562
1779
  quota.resetsAt !== void 0 && quota.resetsAt > current ? t("usageMeterReset", { duration: resetCountdownParts(quota.resetsAt - current).map((part) => t(UNIT_KEYS[part.unit][part.count === 1 ? 0 : 1], { count: part.count })).join(" ") }) : ""
1563
1780
  ].filter(Boolean).join(" · ");
1564
1781
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1579,7 +1796,7 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
1579
1796
  height: "100%",
1580
1797
  borderRadius: "inherit",
1581
1798
  width: `${used}%`,
1582
- background: usageBarColor(quota.usedPercent, elapsed, preset, fresh)
1799
+ background: usageBarColor(quota.usedPercent, pace, preset, fresh)
1583
1800
  } }), elapsed !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1584
1801
  "aria-hidden": "true",
1585
1802
  "data-usage-time-marker": true,
@@ -1623,6 +1840,21 @@ const SOURCES = [
1623
1840
  function errorText(error) {
1624
1841
  return error instanceof Error ? error.message : String(error);
1625
1842
  }
1843
+ /**
1844
+ * Row label of one window on an external usage card. A session or weekly
1845
+ * window appends the model it belongs to when the provider names one (MiniMax
1846
+ * reports a window per model), so two models' weekly rows stay distinguishable.
1847
+ * The monthly pool is a scope on an `other` window, not a model.
1848
+ * @param t - section translate.
1849
+ * @param window - the reported window.
1850
+ * @returns the localized label, e.g. "Weekly · general".
1851
+ */
1852
+ function externalUsageWindowLabel(t, window$1) {
1853
+ const named = window$1.scope !== void 0 && window$1.scope !== "";
1854
+ if (window$1.kind === "other") return window$1.scope === "Monthly" ? t("usageMonthly") : named ? window$1.scope : t("usageWindow");
1855
+ const base = window$1.kind === "session" ? t("usageSession") : t("usageWeekly");
1856
+ return named ? `${base} · ${window$1.scope}` : base;
1857
+ }
1626
1858
  /** Quota cards for API-key providers already available through DSH itself. */
1627
1859
  function ExternalUsageCards({ rpc, t }) {
1628
1860
  const [status, setStatus] = (0, react.useState)();
@@ -1755,7 +1987,7 @@ function ExternalUsageCards({ rpc, t }) {
1755
1987
  }),
1756
1988
  snapshot?.windows?.map((window$1, index) => {
1757
1989
  const percent = displayUsedPercent(window$1.usedPercent);
1758
- const label = window$1.kind === "session" ? t("usageSession") : window$1.kind === "weekly" ? t("usageWeekly") : window$1.scope === "Monthly" ? t("usageMonthly") : window$1.scope ?? t("usageWindow");
1990
+ const label = externalUsageWindowLabel(t, window$1);
1759
1991
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1760
1992
  style: subscriptionCardStyles.usageRow,
1761
1993
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2032,6 +2264,216 @@ function CursorCard({ rpc, t }) {
2032
2264
  });
2033
2265
  }
2034
2266
 
2267
+ //#endregion
2268
+ //#region src/client/reset-credits-view.tsx
2269
+ const STATE_KEYS = {
2270
+ available: "resetCreditAvailable",
2271
+ notUsable: "resetCreditNotUsable",
2272
+ paused: "resetCreditPaused",
2273
+ exhausted: "resetCreditExhausted",
2274
+ expired: "resetCreditExpired"
2275
+ };
2276
+ /**
2277
+ * Classify one grant. Expired outranks every other flag. A future
2278
+ * `cooldownUntil` or `usableNow: false` is not usable even when `claimable`
2279
+ * is true. `claimable` does not change the word: it is not an action.
2280
+ */
2281
+ function resetCreditState(credit, now) {
2282
+ if (credit.expiresAt !== void 0 && credit.expiresAt <= now) return "expired";
2283
+ if (credit.paused === true) return "paused";
2284
+ if (credit.resetsLeft === 0) return "exhausted";
2285
+ if (credit.cooldownUntil !== void 0 && credit.cooldownUntil > now || credit.usableNow === false) return "notUsable";
2286
+ return "available";
2287
+ }
2288
+ /**
2289
+ * Headline count. Codex passes one entry per credit, so the count is the
2290
+ * list length. Claude passes one entry per grant, so the count is the sum of
2291
+ * `resetsLeft` on grants that are neither expired nor paused. A missing
2292
+ * `resetsLeft` adds nothing; the row says the count is unavailable.
2293
+ */
2294
+ function resetCreditsAvailableCount(credits, now, mode) {
2295
+ if (mode === "credits") return credits.length;
2296
+ let sum = 0;
2297
+ for (const credit of credits) {
2298
+ const state = resetCreditState(credit, now);
2299
+ if (state === "expired" || state === "paused") continue;
2300
+ if (typeof credit.resetsLeft === "number" && Number.isFinite(credit.resetsLeft) && credit.resetsLeft > 0) sum += credit.resetsLeft;
2301
+ }
2302
+ return sum;
2303
+ }
2304
+ function expiryText(t, credit) {
2305
+ return credit.expiresAt === void 0 ? t("resetCreditExpiryUnknown") : t("resetCreditExpires", { date: new Date(credit.expiresAt).toLocaleString() });
2306
+ }
2307
+ function remainingText(t, credit) {
2308
+ const left = credit.resetsLeft;
2309
+ const total = credit.resetsTotal;
2310
+ const hasLeft = typeof left === "number" && Number.isFinite(left);
2311
+ if (hasLeft && typeof total === "number" && Number.isFinite(total)) return t("resetCreditRemaining", {
2312
+ left,
2313
+ total
2314
+ });
2315
+ if (hasLeft) return t("resetCreditLeft", { left });
2316
+ return t("resetCreditRemainingUnknown");
2317
+ }
2318
+ /** Accessible name of one Claude grant row: state, remaining, and expiry, not a control name. */
2319
+ function resetCreditRowName(t, credit, now) {
2320
+ const state = resetCreditState(credit, now);
2321
+ const parts = [
2322
+ t("resetCreditFull"),
2323
+ t(STATE_KEYS[state]),
2324
+ remainingText(t, credit),
2325
+ expiryText(t, credit)
2326
+ ];
2327
+ if (state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now) parts.push(t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }));
2328
+ return parts.join(", ");
2329
+ }
2330
+ /** Claude omits an empty grant list; Codex still shows a defined, possibly empty, list. */
2331
+ function showsResetCredits(provider, credits) {
2332
+ if (provider === "codex") return credits !== void 0;
2333
+ return (credits?.length ?? 0) > 0;
2334
+ }
2335
+ /** The error line the settings card mounts. Claude never interpolates provider text. */
2336
+ function ResetCreditsErrorLine({ provider, message: message$1, t }) {
2337
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2338
+ style: subscriptionCardStyles.error,
2339
+ children: resetCreditsErrorText(provider, message$1, t)
2340
+ });
2341
+ }
2342
+ /** Fixed sentence for Claude. Codex may include the lookup message; Claude must not. */
2343
+ function resetCreditsErrorText(provider, message$1, t) {
2344
+ return provider === "claude" ? t("resetCreditsErrorFixed") : t("resetCreditsError", { message: message$1 });
2345
+ }
2346
+ const summaryStyle = {
2347
+ ...subscriptionCardStyles.usageMeta,
2348
+ cursor: "pointer",
2349
+ listStyle: "none",
2350
+ justifyContent: "flex-start",
2351
+ gap: 6
2352
+ };
2353
+ /**
2354
+ * The closed disclosure both providers share. Claude rows are plain text:
2355
+ * no button, no `role="button"`, and `cursor: default`, including when
2356
+ * `claimable` is true.
2357
+ */
2358
+ function ResetCreditsDisclosure({ mode, credits, t, now = Date.now(), nextCredit, useAction, statusMessage }) {
2359
+ const count = resetCreditsAvailableCount(credits, now, mode);
2360
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
2361
+ className: "subscriptions-reset-credits",
2362
+ style: subscriptionCardStyles.usageRow,
2363
+ children: [
2364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
2365
+ style: summaryStyle,
2366
+ children: [
2367
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2368
+ "aria-hidden": "true",
2369
+ width: "12",
2370
+ height: "12",
2371
+ viewBox: "0 0 16 16",
2372
+ style: { flexShrink: 0 },
2373
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2374
+ d: "m6 3 5 5-5 5",
2375
+ fill: "none",
2376
+ stroke: "currentColor",
2377
+ strokeWidth: "1.5",
2378
+ strokeLinecap: "round",
2379
+ strokeLinejoin: "round"
2380
+ })
2381
+ }),
2382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCreditsTitle") }),
2383
+ mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCreditReadOnlyLabel") }),
2384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2385
+ style: { marginLeft: "auto" },
2386
+ children: t("resetCreditsAvailable", { count })
2387
+ })
2388
+ ]
2389
+ }),
2390
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: ".subscriptions-reset-credits > summary::-webkit-details-marker { display: none; }.subscriptions-reset-credits[open] > summary > svg { transform: rotate(90deg); }" }),
2391
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2392
+ style: {
2393
+ paddingLeft: 18,
2394
+ marginTop: 6
2395
+ },
2396
+ children: [
2397
+ mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2398
+ style: subscriptionCardStyles.status,
2399
+ children: t("resetCreditReadOnly")
2400
+ }),
2401
+ credits.map((credit, index) => {
2402
+ const state = resetCreditState(credit, now);
2403
+ const showUse = mode === "credits" && useAction !== void 0 && credit === nextCredit;
2404
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2405
+ "data-reset-state": mode === "grants" ? state : void 0,
2406
+ "data-reset-claimable": mode === "grants" && credit.claimable === true ? "true" : void 0,
2407
+ role: mode === "grants" ? "group" : void 0,
2408
+ "aria-label": mode === "grants" ? resetCreditRowName(t, credit, now) : void 0,
2409
+ style: {
2410
+ ...subscriptionCardStyles.usageMeta,
2411
+ minHeight: 28,
2412
+ gap: 12,
2413
+ cursor: mode === "grants" ? "default" : void 0
2414
+ },
2415
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2416
+ style: {
2417
+ display: "inline-flex",
2418
+ alignItems: "center",
2419
+ gap: 8,
2420
+ flexShrink: 0
2421
+ },
2422
+ children: [
2423
+ t("resetCreditFull"),
2424
+ mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2425
+ "aria-hidden": "true",
2426
+ "data-reset-state-word": state,
2427
+ children: t(STATE_KEYS[state])
2428
+ }),
2429
+ showUse && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2430
+ type: "button",
2431
+ "aria-disabled": useAction.disabled,
2432
+ title: useAction.title,
2433
+ style: {
2434
+ minHeight: 22,
2435
+ height: 22,
2436
+ padding: "0 7px",
2437
+ borderRadius: 6,
2438
+ opacity: useAction.disabled ? .35 : 1,
2439
+ cursor: useAction.disabled ? "not-allowed" : "pointer"
2440
+ },
2441
+ onClick: () => {
2442
+ if (!useAction.disabled) useAction.onUse();
2443
+ },
2444
+ children: useAction.busy ? t("resetUseChecking") : useAction.label
2445
+ })
2446
+ ]
2447
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2448
+ style: { textAlign: "right" },
2449
+ children: [
2450
+ mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2451
+ "aria-hidden": "true",
2452
+ children: [remainingText(t, credit), " · "]
2453
+ }),
2454
+ mode === "grants" && state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2455
+ "aria-hidden": "true",
2456
+ children: [t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }), " · "]
2457
+ }),
2458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2459
+ "aria-hidden": mode === "grants" ? true : void 0,
2460
+ children: expiryText(t, credit)
2461
+ })
2462
+ ]
2463
+ })]
2464
+ }, credit.id ?? index);
2465
+ }),
2466
+ statusMessage !== void 0 && statusMessage !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2467
+ role: "status",
2468
+ style: subscriptionCardStyles.status,
2469
+ children: statusMessage
2470
+ })
2471
+ ]
2472
+ })
2473
+ ]
2474
+ });
2475
+ }
2476
+
2035
2477
  //#endregion
2036
2478
  //#region src/client/UsageBadgeDisplaySetting.tsx
2037
2479
  /** Browser-local display preference; never changes provider or account settings. */
@@ -2050,7 +2492,7 @@ function UsageBadgeDisplaySetting({ t }) {
2050
2492
  value: mode,
2051
2493
  onChange: (event) => {
2052
2494
  const value = event.currentTarget.value;
2053
- if (value !== "recent" && value !== "hidden") return;
2495
+ if (value !== "recent" && value !== "always" && value !== "hidden") return;
2054
2496
  try {
2055
2497
  setUsageBadgeMode(value);
2056
2498
  setFailed(false);
@@ -2059,13 +2501,20 @@ function UsageBadgeDisplaySetting({ t }) {
2059
2501
  setFailed(true);
2060
2502
  }
2061
2503
  },
2062
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2063
- value: "recent",
2064
- children: t("usageBadgeDisplayRecent")
2065
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2066
- value: "hidden",
2067
- children: t("usageBadgeDisplayHidden")
2068
- })]
2504
+ children: [
2505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2506
+ value: "recent",
2507
+ children: t("usageBadgeDisplayRecent")
2508
+ }),
2509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2510
+ value: "always",
2511
+ children: t("usageBadgeDisplayAlways")
2512
+ }),
2513
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2514
+ value: "hidden",
2515
+ children: t("usageBadgeDisplayHidden")
2516
+ })
2517
+ ]
2069
2518
  })]
2070
2519
  }),
2071
2520
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -2122,8 +2571,9 @@ function UsageBadgeDisplaySetting({ t }) {
2122
2571
  }
2123
2572
  const styles$6 = {
2124
2573
  card: {
2125
- border: "1px solid var(--dsw-alias-border-l2)",
2126
- borderRadius: 12,
2574
+ border: "0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))",
2575
+ borderRadius: "var(--dsw-radius-xl, 20px)",
2576
+ background: "var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))",
2127
2577
  padding: "12px 14px",
2128
2578
  display: "flex",
2129
2579
  flexDirection: "column",
@@ -2132,21 +2582,12 @@ const styles$6 = {
2132
2582
  field: {
2133
2583
  display: "flex",
2134
2584
  flexDirection: "column",
2135
- gap: 4,
2136
- fontSize: 14,
2137
- lineHeight: "22px"
2138
- },
2139
- select: {
2140
- height: 32,
2141
- width: "100%",
2142
- boxSizing: "border-box",
2143
- border: "1px solid var(--dsw-alias-border-l2)",
2144
- borderRadius: 8,
2145
- padding: "0 10px",
2146
- font: "inherit",
2147
- background: "var(--dsw-alias-bg-layer-1)",
2148
- color: "var(--dsw-alias-label-primary)"
2585
+ gap: 6,
2586
+ fontSize: 13,
2587
+ fontWeight: 500,
2588
+ lineHeight: "20px"
2149
2589
  },
2590
+ select: { width: "100%" },
2150
2591
  hint: {
2151
2592
  margin: 0,
2152
2593
  fontSize: 12,
@@ -2226,7 +2667,12 @@ const styles$5 = {
2226
2667
  fontSize: 14,
2227
2668
  lineHeight: "22px"
2228
2669
  },
2229
- card: subscriptionCardStyles.card,
2670
+ card: {
2671
+ ...subscriptionCardStyles.card,
2672
+ border: "0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))",
2673
+ borderRadius: "var(--dsw-radius-xl, 20px)",
2674
+ background: "var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))"
2675
+ },
2230
2676
  cardHeader: subscriptionCardStyles.header,
2231
2677
  dot: subscriptionCardStyles.dot,
2232
2678
  name: subscriptionCardStyles.name,
@@ -2239,21 +2685,32 @@ const styles$5 = {
2239
2685
  },
2240
2686
  errorLine: subscriptionCardStyles.error,
2241
2687
  actions: subscriptionCardStyles.actions,
2242
- button: subscriptionCardStyles.button,
2243
- usage: subscriptionCardStyles.usage,
2688
+ usage: {
2689
+ ...subscriptionCardStyles.usage,
2690
+ borderTop: "0.5px solid var(--dsw-alias-border-l2)"
2691
+ },
2244
2692
  usageHeader: subscriptionCardStyles.usageHeader,
2245
2693
  usageTitle: subscriptionCardStyles.usageTitle,
2246
2694
  usagePlan: subscriptionCardStyles.usagePlan,
2247
- usageRefresh: subscriptionCardStyles.usageRefresh,
2695
+ usageRefresh: {
2696
+ minHeight: 22,
2697
+ height: 22,
2698
+ padding: "0 8px",
2699
+ marginLeft: "auto"
2700
+ },
2248
2701
  usageRow: subscriptionCardStyles.usageRow,
2249
2702
  usageMeta: subscriptionCardStyles.usageMeta,
2250
- accountRow: subscriptionCardStyles.account,
2703
+ accountRow: {
2704
+ ...subscriptionCardStyles.account,
2705
+ border: "0.5px solid var(--dsw-alias-border-l2)",
2706
+ borderRadius: "var(--dsw-radius-lg, 16px)"
2707
+ },
2251
2708
  accountHeader: subscriptionCardStyles.accountHeader,
2252
2709
  accountName: subscriptionCardStyles.accountName,
2253
2710
  starButton: {
2254
2711
  border: "none",
2255
- background: "transparent",
2256
2712
  padding: 0,
2713
+ width: 28,
2257
2714
  font: "inherit",
2258
2715
  fontSize: 14,
2259
2716
  lineHeight: "20px",
@@ -2265,8 +2722,8 @@ const styles$5 = {
2265
2722
  display: "flex",
2266
2723
  flexDirection: "column",
2267
2724
  gap: 6,
2268
- border: "1px solid var(--dsw-alias-border-l2)",
2269
- borderRadius: 8,
2725
+ border: "0.5px solid var(--dsw-alias-border-l2)",
2726
+ borderRadius: "var(--dsw-radius-lg, 16px)",
2270
2727
  padding: "10px 12px",
2271
2728
  background: "var(--dsw-alias-bg-layer-1)"
2272
2729
  },
@@ -2278,6 +2735,21 @@ const styles$5 = {
2278
2735
  color: "var(--dsw-alias-label-primary)",
2279
2736
  userSelect: "all"
2280
2737
  },
2738
+ manual: {
2739
+ fontSize: 13,
2740
+ lineHeight: "20px"
2741
+ },
2742
+ manualRow: {
2743
+ display: "flex",
2744
+ flexWrap: "wrap",
2745
+ gap: 8,
2746
+ alignItems: "center",
2747
+ marginTop: 8
2748
+ },
2749
+ manualInput: {
2750
+ flex: "1 1 200px",
2751
+ minWidth: 0
2752
+ },
2281
2753
  modalOverlay: {
2282
2754
  position: "fixed",
2283
2755
  inset: 0,
@@ -2286,7 +2758,8 @@ const styles$5 = {
2286
2758
  alignItems: "center",
2287
2759
  justifyContent: "center",
2288
2760
  padding: 16,
2289
- background: "rgba(0, 0, 0, 0.45)"
2761
+ background: "var(--dsw-alias-bg-mask-1, #0006)",
2762
+ backdropFilter: "var(--dsw-mask-blur, none)"
2290
2763
  },
2291
2764
  modal: {
2292
2765
  width: 460,
@@ -2298,9 +2771,10 @@ const styles$5 = {
2298
2771
  flexDirection: "column",
2299
2772
  gap: 12,
2300
2773
  padding: "16px 18px",
2301
- borderRadius: 12,
2302
- background: "var(--dsw-alias-bg-layer-1)",
2303
- border: "1px solid var(--dsw-alias-border-l2)"
2774
+ borderRadius: "var(--dsw-radius-panel, 28px)",
2775
+ background: "var(--dsw-alias-bg-layer-2)",
2776
+ border: 0,
2777
+ boxShadow: "var(--dsw-elevation-prominent)"
2304
2778
  },
2305
2779
  modalHeader: {
2306
2780
  display: "flex",
@@ -2308,9 +2782,9 @@ const styles$5 = {
2308
2782
  gap: 8
2309
2783
  },
2310
2784
  modalTitle: {
2311
- fontWeight: 600,
2312
- fontSize: 15,
2313
- lineHeight: "22px",
2785
+ fontWeight: 500,
2786
+ fontSize: 16,
2787
+ lineHeight: "24px",
2314
2788
  color: "var(--dsw-alias-label-primary)"
2315
2789
  }
2316
2790
  };
@@ -2380,10 +2854,22 @@ function SubscriptionsSection(props) {
2380
2854
  const [observedAt, setObservedAt] = (0, react.useState)({});
2381
2855
  const [usageErrors, setUsageErrors] = (0, react.useState)({});
2382
2856
  const [usageLoading, setUsageLoading] = (0, react.useState)({});
2857
+ const [resetConfirmation, setResetConfirmation] = (0, react.useState)();
2858
+ const [resetBusy, setResetBusy] = (0, react.useState)(false);
2859
+ const [resetAcknowledged, setResetAcknowledged] = (0, react.useState)(false);
2860
+ const [resetMessage, setResetMessage] = (0, react.useState)("");
2861
+ const [resetAccount, setResetAccount] = (0, react.useState)();
2862
+ const resetDialogRef = (0, react.useRef)(null);
2863
+ (0, react.useEffect)(() => {
2864
+ if (resetConfirmation) resetDialogRef.current?.showModal();
2865
+ else resetDialogRef.current?.close();
2866
+ }, [resetConfirmation]);
2867
+ const resetInflight = (0, react.useRef)(false);
2383
2868
  const mountedRef = (0, react.useRef)(true);
2384
2869
  const pollersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2385
2870
  /** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
2386
- const usageInflightRef = (0, react.useRef)(/* @__PURE__ */ new Set());
2871
+ /** In-flight usage reads per key, holding the promise so a forced read can wait one out. */
2872
+ const usageInflightRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2387
2873
  const usageRosterSignatureRef = (0, react.useRef)();
2388
2874
  const [managedProvider, setManagedProvider] = (0, react.useState)();
2389
2875
  const setProviderError = (0, react.useCallback)((provider, message$1) => {
@@ -2460,47 +2946,97 @@ function SubscriptionsSection(props) {
2460
2946
  };
2461
2947
  }, [refresh, startPolling]);
2462
2948
  const loadUsage = (0, react.useCallback)(async (provider, account, force = false) => {
2949
+ if (rpc === void 0) return;
2463
2950
  const key = `${provider}:${account}`;
2951
+ const pending = usageInflightRef.current.get(key);
2952
+ if (pending !== void 0) {
2953
+ if (!force) return;
2954
+ await pending;
2955
+ }
2464
2956
  if (rpc === void 0 || usageInflightRef.current.has(key)) return;
2465
- usageInflightRef.current.add(key);
2466
- setUsageLoading((prev) => ({
2467
- ...prev,
2468
- [key]: true
2469
- }));
2470
- try {
2471
- const usage = await callSubscriptionsAuth(rpc, "usage", {
2472
- provider,
2473
- account,
2474
- ...force ? { force: true } : {}
2475
- });
2476
- if (!mountedRef.current) return;
2477
- setUsages((prev) => ({
2957
+ const read = (async () => {
2958
+ setUsageLoading((prev) => ({
2478
2959
  ...prev,
2479
- [key]: usage
2960
+ [key]: true
2480
2961
  }));
2481
- setObservedAt((prev) => ({
2482
- ...prev,
2483
- [key]: usage.observedAt ?? Date.now()
2484
- }));
2485
- window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
2486
- setUsageErrors((prev) => {
2487
- const next = { ...prev };
2488
- delete next[key];
2489
- return next;
2962
+ try {
2963
+ const usage = await callSubscriptionsAuth(rpc, "usage", {
2964
+ provider,
2965
+ account,
2966
+ ...force ? { force: true } : {}
2967
+ });
2968
+ if (!mountedRef.current) return;
2969
+ setUsages((prev) => ({
2970
+ ...prev,
2971
+ [key]: usage
2972
+ }));
2973
+ setObservedAt((prev) => ({
2974
+ ...prev,
2975
+ [key]: usage.observedAt ?? Date.now()
2976
+ }));
2977
+ window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
2978
+ setUsageErrors((prev) => {
2979
+ const next = { ...prev };
2980
+ delete next[key];
2981
+ return next;
2982
+ });
2983
+ } catch (error) {
2984
+ if (mountedRef.current) setUsageErrors((prev) => ({
2985
+ ...prev,
2986
+ [key]: messageOf(error)
2987
+ }));
2988
+ } finally {
2989
+ usageInflightRef.current.delete(key);
2990
+ if (mountedRef.current) setUsageLoading((prev) => ({
2991
+ ...prev,
2992
+ [key]: false
2993
+ }));
2994
+ }
2995
+ })();
2996
+ usageInflightRef.current.set(key, read);
2997
+ await read;
2998
+ }, [rpc]);
2999
+ async function prepareReset(account) {
3000
+ if (!rpc || resetInflight.current) return;
3001
+ resetInflight.current = true;
3002
+ setResetBusy(true);
3003
+ setResetAccount(account);
3004
+ setResetConfirmation(void 0);
3005
+ setResetMessage("");
3006
+ try {
3007
+ const result = await callSubscriptionsAuth(rpc, "prepareReset", { account });
3008
+ setResetAcknowledged(false);
3009
+ setResetConfirmation({
3010
+ ...result,
3011
+ account
2490
3012
  });
2491
3013
  } catch (error) {
2492
- if (mountedRef.current) setUsageErrors((prev) => ({
2493
- ...prev,
2494
- [key]: messageOf(error)
2495
- }));
3014
+ setResetMessage(messageOf(error));
2496
3015
  } finally {
2497
- usageInflightRef.current.delete(key);
2498
- if (mountedRef.current) setUsageLoading((prev) => ({
2499
- ...prev,
2500
- [key]: false
2501
- }));
3016
+ resetInflight.current = false;
3017
+ setResetBusy(false);
2502
3018
  }
2503
- }, [rpc]);
3019
+ }
3020
+ async function consumeReset() {
3021
+ if (!rpc || !resetConfirmation || !resetAcknowledged || resetInflight.current) return;
3022
+ const { account, ticket } = resetConfirmation;
3023
+ resetInflight.current = true;
3024
+ setResetBusy(true);
3025
+ setResetConfirmation(void 0);
3026
+ try {
3027
+ await callSubscriptionsAuth(rpc, "consumeReset", {
3028
+ account,
3029
+ ticket
3030
+ });
3031
+ setResetMessage(t("resetUseSuccess"));
3032
+ } catch (error) {
3033
+ setResetMessage(messageOf(error));
3034
+ } finally {
3035
+ await loadUsage("codex", account, true);
3036
+ resetInflight.current = false;
3037
+ setResetBusy(false);
3038
+ }
3039
+ }
2504
3040
  (0, react.useEffect)(() => {
2505
3041
  const live = /* @__PURE__ */ new Set();
2506
3042
  for (const { id } of PROVIDERS) for (const account of statuses[id]?.accounts ?? []) {
@@ -2648,8 +3184,10 @@ function SubscriptionsSection(props) {
2648
3184
  children: t("unavailable")
2649
3185
  });
2650
3186
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3187
+ className: "dsh-subscriptions-settings",
2651
3188
  style: styles$5.section,
2652
3189
  children: [
3190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: providerSettingsCss }),
2653
3191
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2654
3192
  style: styles$5.intro,
2655
3193
  children: t("intro")
@@ -2698,6 +3236,10 @@ function SubscriptionsSection(props) {
2698
3236
  const usage = usages[usageKey];
2699
3237
  const usageError = usageErrors[usageKey];
2700
3238
  const display = account.account ?? account.key;
3239
+ const sortedCredits = [...usage?.resetCredits ?? []].sort((a, b) => (a.expiresAt ?? Infinity) - (b.expiresAt ?? Infinity));
3240
+ const nextReset = sortedCredits.find((c) => c.expiresAt === void 0 || c.expiresAt > Date.now());
3241
+ const resetBlock = usage ? resetActionBlock(usage) : "resetUseRefresh";
3242
+ const resetDisabled = resetBusy || resetBlock !== void 0;
2701
3243
  const showUsage = usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[usageKey] === true);
2702
3244
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2703
3245
  style: styles$5.accountRow,
@@ -2728,7 +3270,6 @@ function SubscriptionsSection(props) {
2728
3270
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2729
3271
  type: "button",
2730
3272
  style: {
2731
- ...styles$5.button,
2732
3273
  marginLeft: "auto",
2733
3274
  flexShrink: 0
2734
3275
  },
@@ -2754,13 +3295,7 @@ function SubscriptionsSection(props) {
2754
3295
  }),
2755
3296
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2756
3297
  type: "button",
2757
- style: {
2758
- ...styles$5.usageRefresh,
2759
- ...usageLoading[usageKey] === true ? {
2760
- opacity: .5,
2761
- cursor: "default"
2762
- } : {}
2763
- },
3298
+ style: styles$5.usageRefresh,
2764
3299
  disabled: usageLoading[usageKey] === true,
2765
3300
  onClick: () => {
2766
3301
  loadUsage(id, account.key, true);
@@ -2795,6 +3330,28 @@ function SubscriptionsSection(props) {
2795
3330
  stale: usageError !== void 0 || usage.stale === true
2796
3331
  })]
2797
3332
  }, index);
3333
+ }),
3334
+ (id === "codex" || id === "claude") && usage?.resetCreditsError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditsErrorLine, {
3335
+ provider: id,
3336
+ message: usage.resetCreditsError,
3337
+ t
3338
+ }),
3339
+ (id === "codex" || id === "claude") && showsResetCredits(id, usage?.resetCredits) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditsDisclosure, {
3340
+ mode: id === "claude" ? "grants" : "credits",
3341
+ credits: sortedCredits,
3342
+ t,
3343
+ ...nextReset !== void 0 ? { nextCredit: nextReset } : {},
3344
+ ...id === "codex" ? { useAction: {
3345
+ accountKey: account.key,
3346
+ busy: resetBusy && resetAccount === account.key,
3347
+ disabled: resetDisabled,
3348
+ title: t(resetBusy ? "resetUseChecking" : resetBlock ?? "resetUseReady"),
3349
+ label: t("resetUseButton"),
3350
+ onUse: () => {
3351
+ if (!resetDisabled) prepareReset(account.key);
3352
+ }
3353
+ } } : {},
3354
+ ...resetAccount === account.key && resetMessage !== "" ? { statusMessage: resetMessage } : {}
2798
3355
  })
2799
3356
  ]
2800
3357
  })]
@@ -2805,7 +3362,6 @@ function SubscriptionsSection(props) {
2805
3362
  children: [
2806
3363
  !busy && accounts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2807
3364
  type: "button",
2808
- style: styles$5.button,
2809
3365
  onClick: () => {
2810
3366
  login(id);
2811
3367
  },
@@ -2813,14 +3369,12 @@ function SubscriptionsSection(props) {
2813
3369
  }),
2814
3370
  !busy && accounts.length > 0 && id === "claude" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2815
3371
  type: "button",
2816
- style: styles$5.button,
2817
3372
  onClick: () => {
2818
3373
  login(id, "oauth");
2819
3374
  },
2820
3375
  children: t("addAccountOAuth")
2821
3376
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2822
3377
  type: "button",
2823
- style: styles$5.button,
2824
3378
  onClick: () => {
2825
3379
  login(id, "keychain");
2826
3380
  },
@@ -2828,7 +3382,6 @@ function SubscriptionsSection(props) {
2828
3382
  })] }),
2829
3383
  !busy && accounts.length > 0 && id !== "claude" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2830
3384
  type: "button",
2831
- style: styles$5.button,
2832
3385
  onClick: () => {
2833
3386
  login(id);
2834
3387
  },
@@ -2836,7 +3389,6 @@ function SubscriptionsSection(props) {
2836
3389
  }),
2837
3390
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2838
3391
  type: "button",
2839
- style: styles$5.button,
2840
3392
  "aria-haspopup": "dialog",
2841
3393
  onClick: () => setManagedProvider({
2842
3394
  id,
@@ -2846,7 +3398,6 @@ function SubscriptionsSection(props) {
2846
3398
  }),
2847
3399
  busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2848
3400
  type: "button",
2849
- style: styles$5.button,
2850
3401
  onClick: () => {
2851
3402
  cancel(id);
2852
3403
  },
@@ -2873,14 +3424,12 @@ function SubscriptionsSection(props) {
2873
3424
  style: styles$5.actions,
2874
3425
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2875
3426
  type: "button",
2876
- style: styles$5.button,
2877
3427
  onClick: () => {
2878
3428
  copyDeviceCode(id, deviceCode.userCode);
2879
3429
  },
2880
3430
  children: copiedCode === id ? t("deviceCodeCopied") : t("deviceCodeCopy")
2881
3431
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2882
3432
  type: "button",
2883
- style: styles$5.button,
2884
3433
  onClick: () => {
2885
3434
  window.open(deviceCode.verificationUrl, "_blank", "noopener");
2886
3435
  },
@@ -2903,7 +3452,6 @@ function SubscriptionsSection(props) {
2903
3452
  }))
2904
3453
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2905
3454
  type: "button",
2906
- style: styles$5.button,
2907
3455
  onClick: () => {
2908
3456
  submitManual(id);
2909
3457
  },
@@ -2928,6 +3476,90 @@ function SubscriptionsSection(props) {
2928
3476
  rpc,
2929
3477
  t,
2930
3478
  onClose: () => setManagedProvider(void 0)
3479
+ }),
3480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: ".subscriptions-reset-dialog::backdrop { background: var(--dsw-alias-bg-mask-1, #0006); backdrop-filter: var(--dsw-mask-blur, none); }" }),
3481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", {
3482
+ ref: resetDialogRef,
3483
+ className: "subscriptions-reset-dialog",
3484
+ "aria-labelledby": "subscriptions-reset-title",
3485
+ onCancel: () => setResetConfirmation(void 0),
3486
+ onClose: () => setResetConfirmation(void 0),
3487
+ style: {
3488
+ ...styles$5.modal,
3489
+ display: resetConfirmation ? "flex" : "none",
3490
+ margin: "auto",
3491
+ color: "var(--dsw-alias-label-primary)"
3492
+ },
3493
+ children: resetConfirmation && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3495
+ id: "subscriptions-reset-title",
3496
+ style: {
3497
+ ...styles$5.modalTitle,
3498
+ margin: 0
3499
+ },
3500
+ children: t("resetUseConfirmTitle")
3501
+ }),
3502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3503
+ style: {
3504
+ ...styles$5.statusLine,
3505
+ overflowWrap: "anywhere"
3506
+ },
3507
+ children: statuses.codex?.accounts.find((a) => a.key === resetConfirmation.account)?.account ?? resetConfirmation.account
3508
+ }),
3509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3510
+ style: {
3511
+ ...styles$5.usageMeta,
3512
+ gap: 12
3513
+ },
3514
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("usageWeekly") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", { children: [Math.round(resetConfirmation.weeklyUsedPercent), "%"] })]
3515
+ }),
3516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3517
+ style: styles$5.usageMeta,
3518
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: resetConfirmation.creditExpiresAt === void 0 ? t("resetCreditExpiryUnknown") : t("resetCreditExpires", { date: new Date(resetConfirmation.creditExpiresAt).toLocaleString() }) })
3519
+ }),
3520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3521
+ style: {
3522
+ ...styles$5.statusLine,
3523
+ margin: "4px 0"
3524
+ },
3525
+ children: t("resetUseWarning")
3526
+ }),
3527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3528
+ style: {
3529
+ display: "flex",
3530
+ gap: 8,
3531
+ alignItems: "flex-start",
3532
+ fontSize: 12
3533
+ },
3534
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3535
+ type: "checkbox",
3536
+ checked: resetAcknowledged,
3537
+ onChange: (e) => setResetAcknowledged(e.target.checked)
3538
+ }), t("resetUseAcknowledge")]
3539
+ }),
3540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3541
+ className: "dsh-subscription-dialog-actions",
3542
+ style: {
3543
+ ...styles$5.actions,
3544
+ justifyContent: "flex-end",
3545
+ marginTop: 8
3546
+ },
3547
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3548
+ type: "button",
3549
+ autoFocus: true,
3550
+ onClick: () => setResetConfirmation(void 0),
3551
+ children: t("cancel")
3552
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3553
+ type: "button",
3554
+ className: "dsh-subscription-primary",
3555
+ disabled: !resetAcknowledged || resetBusy,
3556
+ onClick: () => {
3557
+ consumeReset();
3558
+ },
3559
+ children: t("resetUseConfirm")
3560
+ })]
3561
+ })
3562
+ ] })
2931
3563
  })
2932
3564
  ]
2933
3565
  });
@@ -3214,20 +3846,42 @@ const HOST_CONTRACT = {
3214
3846
  package: "@deepseek-ai/dsh-client-ui-theme",
3215
3847
  names: [
3216
3848
  "--dsw-alias-bg-layer-1",
3849
+ "--dsw-alias-bg-layer-2",
3850
+ "--dsw-alias-bg-layer-3",
3851
+ "--dsw-alias-bg-mask-1",
3217
3852
  "--dsw-alias-bg-skeleton",
3853
+ "--dsw-alias-border-l1",
3218
3854
  "--dsw-alias-border-l2",
3219
3855
  "--dsw-alias-border-l2-darkmode-thin",
3856
+ "--dsw-alias-border-l3",
3857
+ "--dsw-alias-border-l4",
3858
+ "--dsw-alias-brand-primary",
3859
+ "--dsw-alias-button-primary-fill",
3860
+ "--dsw-alias-button-primary-hover",
3861
+ "--dsw-alias-interactive-bg-active",
3220
3862
  "--dsw-alias-interactive-bg-hover",
3221
3863
  "--dsw-alias-interactive-bg-hover-solid",
3222
3864
  "--dsw-alias-label-dimmed",
3223
3865
  "--dsw-alias-label-primary",
3866
+ "--dsw-alias-label-primary-foreground",
3224
3867
  "--dsw-alias-label-secondary",
3225
3868
  "--dsw-alias-label-tertiary",
3869
+ "--dsw-alias-settings-card-fill",
3870
+ "--dsw-alias-settings-card-stroke",
3871
+ "--dsw-alias-state-business-primary",
3226
3872
  "--dsw-alias-state-error-primary",
3227
3873
  "--dsw-alias-state-success-primary",
3228
3874
  "--dsw-alias-state-warn-label",
3229
3875
  "--dsw-elevation-prominent",
3876
+ "--dsw-focus-ring-color",
3877
+ "--dsw-focus-ring-width",
3878
+ "--dsw-mask-blur",
3230
3879
  "--dsw-menu-backdrop-filter",
3880
+ "--dsw-radius-lg",
3881
+ "--dsw-radius-md",
3882
+ "--dsw-radius-panel",
3883
+ "--dsw-radius-sm",
3884
+ "--dsw-radius-xl",
3231
3885
  "--dsw-specific-menu"
3232
3886
  ]
3233
3887
  }
@@ -3765,6 +4419,7 @@ function SpeedSelect({ loadSpeed, setSpeed, t }) {
3765
4419
  const triggerLabel = `${translate("speed")} · ${tierName(state.tier)}`;
3766
4420
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3767
4421
  ref: rootRef,
4422
+ className: "dsh-subscription-speed",
3768
4423
  style: styles$1.root,
3769
4424
  onKeyDown: (event) => {
3770
4425
  if (event.key === "Escape" && open) {
@@ -3772,46 +4427,50 @@ function SpeedSelect({ loadSpeed, setSpeed, t }) {
3772
4427
  setOpen(false);
3773
4428
  }
3774
4429
  },
3775
- children: [open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3776
- style: styles$1.menu,
3777
- role: "menu",
3778
- "aria-label": translate("speed"),
3779
- children: TIERS.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4430
+ children: [
4431
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: subscriptionChromeCss }),
4432
+ open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4433
+ style: styles$1.menu,
4434
+ role: "menu",
4435
+ "aria-label": translate("speed"),
4436
+ children: TIERS.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4437
+ type: "button",
4438
+ role: "menuitemradio",
4439
+ "aria-checked": tier === state.tier,
4440
+ style: styles$1.item,
4441
+ disabled: busy,
4442
+ onClick: () => {
4443
+ choose(tier);
4444
+ },
4445
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4446
+ style: styles$1.itemCheck,
4447
+ children: tier === state.tier ? "✓" : ""
4448
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4449
+ style: styles$1.itemText,
4450
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4451
+ style: styles$1.itemName,
4452
+ children: tierName(tier)
4453
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4454
+ style: styles$1.itemDescription,
4455
+ children: tierDescription(tier)
4456
+ })]
4457
+ })]
4458
+ }, tier))
4459
+ }),
4460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3780
4461
  type: "button",
3781
- role: "menuitemradio",
3782
- "aria-checked": tier === state.tier,
3783
- style: styles$1.item,
4462
+ style: styles$1.trigger,
4463
+ "aria-haspopup": "menu",
4464
+ "aria-expanded": open,
4465
+ title: triggerLabel,
3784
4466
  disabled: busy,
3785
4467
  onClick: () => {
3786
- choose(tier);
4468
+ if (open) setOpen(false);
4469
+ else show();
3787
4470
  },
3788
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3789
- style: styles$1.itemCheck,
3790
- children: tier === state.tier ? "✓" : ""
3791
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3792
- style: styles$1.itemText,
3793
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3794
- style: styles$1.itemName,
3795
- children: tierName(tier)
3796
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3797
- style: styles$1.itemDescription,
3798
- children: tierDescription(tier)
3799
- })]
3800
- })]
3801
- }, tier))
3802
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3803
- type: "button",
3804
- style: styles$1.trigger,
3805
- "aria-haspopup": "menu",
3806
- "aria-expanded": open,
3807
- title: triggerLabel,
3808
- disabled: busy,
3809
- onClick: () => {
3810
- if (open) setOpen(false);
3811
- else show();
3812
- },
3813
- children: triggerLabel
3814
- })]
4471
+ children: triggerLabel
4472
+ })
4473
+ ]
3815
4474
  });
3816
4475
  }
3817
4476
  const styles$1 = {
@@ -3820,9 +4479,9 @@ const styles$1 = {
3820
4479
  display: "inline-flex"
3821
4480
  },
3822
4481
  trigger: {
3823
- border: "1px solid var(--dsw-alias-border-l2)",
3824
- borderRadius: 8,
3825
- background: "transparent",
4482
+ border: "0.5px solid var(--dsw-alias-border-l3)",
4483
+ borderRadius: "var(--dsw-radius-sm, 8px)",
4484
+ background: "var(--dsh-subscription-speed-hover, transparent)",
3826
4485
  color: "var(--dsw-alias-label-secondary)",
3827
4486
  font: "inherit",
3828
4487
  fontSize: 12,
@@ -3839,9 +4498,10 @@ const styles$1 = {
3839
4498
  minWidth: 180,
3840
4499
  padding: 4,
3841
4500
  zIndex: 20,
3842
- background: "var(--dsw-alias-bg-layer-1)",
3843
- border: "1px solid var(--dsw-alias-border-l2)",
3844
- borderRadius: 8,
4501
+ background: "var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2))",
4502
+ border: 0,
4503
+ boxShadow: "var(--dsw-elevation-prominent)",
4504
+ borderRadius: "var(--dsw-radius-lg, 16px)",
3845
4505
  display: "flex",
3846
4506
  flexDirection: "column",
3847
4507
  gap: 2
@@ -3852,8 +4512,8 @@ const styles$1 = {
3852
4512
  gap: 6,
3853
4513
  width: "100%",
3854
4514
  border: "none",
3855
- borderRadius: 6,
3856
- background: "transparent",
4515
+ borderRadius: "var(--dsw-radius-sm, 8px)",
4516
+ background: "var(--dsh-subscription-speed-hover, transparent)",
3857
4517
  padding: "6px 8px",
3858
4518
  cursor: "pointer",
3859
4519
  font: "inherit",
@@ -3893,6 +4553,8 @@ const UsageBadgeIcon = usageBadgeIcon(__deepseek_ai_dsh_client_ui_primitives);
3893
4553
  const USAGE_POLL_INTERVAL_MS = 15 * 6e4;
3894
4554
  /** How often the badge re-reads the session's current model (model switches arrive only by asking). */
3895
4555
  const MODEL_POLL_INTERVAL_MS = 3e3;
4556
+ /** How long the rotation holds one provider in `always` mode (a display cadence, not a data poll). */
4557
+ const BADGE_ROTATION_INTERVAL_MS = 1e4;
3896
4558
  /** Distance between the trigger's top edge and the dialog's bottom (host stat dialogs use the same). */
3897
4559
  const PANEL_GAP = 8;
3898
4560
  /** Distance kept between the dialog and each viewport edge. */
@@ -4092,6 +4754,24 @@ function collapsedDisplays(displays, current) {
4092
4754
  const match = displays.find((d) => d.provider === current);
4093
4755
  return match === void 0 ? [] : [match];
4094
4756
  }
4757
+ /**
4758
+ * `always` mode: the current model's provider while it reports usage,
4759
+ * otherwise the rotation steps through the providers that do, in display
4760
+ * order, wrapping around. The index is taken modulo the current provider
4761
+ * count, so providers appearing or dropping out stay inside range; the
4762
+ * sequence itself may shift when the roster changes. Unlike `recent`, the
4763
+ * retained subscription does not pin the pill — that retention is exactly
4764
+ * what this mode exists to escape.
4765
+ * @param displays - providers with usage, in display order.
4766
+ * @param current - the provider of the session's current model.
4767
+ * @param step - rotation tick; consecutive ticks advance one provider.
4768
+ */
4769
+ function rotatingDisplay(displays, current, step) {
4770
+ if (displays.length === 0) return [];
4771
+ const match = displays.find((d) => d.provider === current);
4772
+ if (match !== void 0) return [match];
4773
+ return [displays[((Number.isFinite(step) ? Math.floor(step) : 0) % displays.length + displays.length) % displays.length]];
4774
+ }
4095
4775
  /** Order for the expanded dialog: the current provider first, the rest in poll order. */
4096
4776
  function expandedDisplays(displays, current) {
4097
4777
  const match = displays.find((d) => d.provider === current);
@@ -4125,7 +4805,7 @@ function usageWindowLabel(t, window$1) {
4125
4805
  * every provider's accounts and their windows. Returns null when no data is
4126
4806
  * available.
4127
4807
  */
4128
- function SubscriptionUsageBadge({ rpc, currentModel, t }) {
4808
+ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
4129
4809
  const translate = t ?? fallbackTranslate;
4130
4810
  const [displays, setDisplays] = (0, react.useState)([]);
4131
4811
  const [selection, setSelection] = (0, react.useState)(void 0);
@@ -4135,6 +4815,8 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
4135
4815
  const badgeSelection = retainSubscriptionSelection(lastSubscription, selection);
4136
4816
  const [open, setOpen] = (0, react.useState)(false);
4137
4817
  const [hover, setHover] = (0, react.useState)(false);
4818
+ const [rotation, setRotation] = (0, react.useState)(0);
4819
+ const [dialogLead, setDialogLead] = (0, react.useState)(void 0);
4138
4820
  const inflightRef = (0, react.useRef)(false);
4139
4821
  const mountedRef = (0, react.useRef)(true);
4140
4822
  const rootRef = (0, react.useRef)(null);
@@ -4298,99 +4980,119 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
4298
4980
  (0, react.useEffect)(() => {
4299
4981
  if (displayMode === "hidden") setOpen(false);
4300
4982
  }, [displayMode]);
4983
+ (0, react.useEffect)(() => {
4984
+ if (displayMode !== "always") return;
4985
+ const interval = rotationMs !== void 0 && Number.isFinite(rotationMs) && rotationMs > 0 ? rotationMs : BADGE_ROTATION_INTERVAL_MS;
4986
+ const timer = setInterval(() => {
4987
+ setRotation((step) => step + 1);
4988
+ }, interval);
4989
+ return () => {
4990
+ clearInterval(timer);
4991
+ };
4992
+ }, [displayMode, rotationMs]);
4301
4993
  const seat = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4302
4994
  ref: seatRef,
4303
4995
  style: styles.seat,
4304
4996
  "aria-hidden": true
4305
4997
  });
4306
- const collapsed = collapsedDisplays(displays, badgeSelection?.provider);
4998
+ const collapsed = displayMode === "always" ? rotatingDisplay(displays, current, rotation) : collapsedDisplays(displays, badgeSelection?.provider);
4307
4999
  if (displayMode === "hidden" || collapsed.length === 0) return seat;
4308
- const label = compactSegment(collapsed[0], badgeSelection?.model, translate);
4309
- const expanded = expandedDisplays(displays, badgeSelection?.provider);
5000
+ const pillModel = displayMode === "always" ? current === collapsed[0]?.provider ? selection?.model : void 0 : badgeSelection?.model;
5001
+ const label = compactSegment(collapsed[0], pillModel, translate);
5002
+ const dialogProvider = open && displayMode === "always" ? dialogLead : badgeSelection?.provider;
5003
+ const dialogModel = open && displayMode === "always" ? dialogLead === current ? selection?.model : void 0 : badgeSelection?.model;
5004
+ const expanded = expandedDisplays(displays, dialogProvider);
4310
5005
  const title = translate("usageBadgeTitle");
4311
5006
  const toggle = () => {
4312
5007
  const next = !open;
5008
+ setDialogLead(collapsed[0]?.provider);
4313
5009
  setOpen(next);
4314
5010
  if (next) refresh();
4315
5011
  };
4316
5012
  const pill = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4317
5013
  ref: rootRef,
4318
5014
  style: styles.anchor,
4319
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4320
- type: "button",
4321
- style: {
4322
- ...styles.pill,
4323
- ...hover || open ? styles.pillActive : {}
4324
- },
4325
- "aria-haspopup": "dialog",
4326
- "aria-expanded": open,
4327
- "aria-label": `${title} · ${label}`,
4328
- title,
4329
- onMouseEnter: () => {
4330
- setHover(true);
4331
- },
4332
- onMouseLeave: () => {
4333
- setHover(false);
4334
- },
4335
- onClick: toggle,
4336
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBadgeIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4337
- style: styles.label,
4338
- children: label
4339
- })]
4340
- }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4341
- ref: panelRef,
4342
- role: "dialog",
4343
- "aria-label": title,
4344
- style: {
4345
- ...styles.panel,
4346
- ...pos ?? MEASURE_STYLE
4347
- },
4348
- children: [
4349
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4350
- style: styles.title,
4351
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4352
- style: styles.titleLabel,
4353
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBadgeIcon, {}), title]
4354
- })
4355
- }),
4356
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4357
- style: styles.titleRule,
4358
- "aria-hidden": true
4359
- }),
4360
- expanded.map((d, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4361
- style: index === 0 ? void 0 : styles.section,
4362
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4363
- style: styles.providerRow,
4364
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4365
- style: styles.providerName,
4366
- children: [d.name, d.provider === current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4367
- style: styles.currentTag,
4368
- children: translate("usageBadgeCurrent")
5015
+ children: [
5016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: subscriptionChromeCss }),
5017
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5018
+ type: "button",
5019
+ className: "dsh-subscription-usage-pill",
5020
+ style: {
5021
+ ...styles.pill,
5022
+ ...hover || open ? styles.pillActive : {}
5023
+ },
5024
+ "aria-haspopup": "dialog",
5025
+ "aria-expanded": open,
5026
+ "aria-label": `${title} · ${label}`,
5027
+ title,
5028
+ onMouseEnter: () => {
5029
+ setHover(true);
5030
+ },
5031
+ onMouseLeave: () => {
5032
+ setHover(false);
5033
+ },
5034
+ onClick: toggle,
5035
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBadgeIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5036
+ style: styles.label,
5037
+ children: label
5038
+ })]
5039
+ }),
5040
+ open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5041
+ ref: panelRef,
5042
+ role: "dialog",
5043
+ "aria-label": title,
5044
+ className: "dsh-subscription-usage-panel",
5045
+ style: {
5046
+ ...styles.panel,
5047
+ ...pos ?? MEASURE_STYLE
5048
+ },
5049
+ children: [
5050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5051
+ style: styles.title,
5052
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5053
+ style: styles.titleLabel,
5054
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBadgeIcon, {}), title]
5055
+ })
5056
+ }),
5057
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5058
+ style: styles.titleRule,
5059
+ "aria-hidden": true
5060
+ }),
5061
+ expanded.map((d, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5062
+ style: index === 0 ? void 0 : styles.section,
5063
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5064
+ style: styles.providerRow,
5065
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5066
+ style: styles.providerName,
5067
+ children: [d.name, d.provider === current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5068
+ style: styles.currentTag,
5069
+ children: translate("usageBadgeCurrent")
5070
+ })]
5071
+ }), d.accounts.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
5072
+ account: d.accounts[0],
5073
+ translate
4369
5074
  })]
4370
- }), d.accounts.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
4371
- account: d.accounts[0],
4372
- translate
4373
- })]
4374
- }), d.accounts.map((account, accountIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4375
- style: accountIndex === 0 ? void 0 : styles.accountBlock,
4376
- children: [d.accounts.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4377
- style: styles.accountRow,
4378
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
4379
- account,
5075
+ }), d.accounts.map((account, accountIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5076
+ style: accountIndex === 0 ? void 0 : styles.accountBlock,
5077
+ children: [d.accounts.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5078
+ style: styles.accountRow,
5079
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
5080
+ account,
5081
+ translate
5082
+ })
5083
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountWindows, {
5084
+ windows: account.windows,
5085
+ observedAt: account.observedAt,
5086
+ stale: account.stale,
5087
+ model: d.provider === dialogProvider ? dialogModel : void 0,
5088
+ provider: d.provider,
4380
5089
  translate
4381
- })
4382
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountWindows, {
4383
- windows: account.windows,
4384
- observedAt: account.observedAt,
4385
- stale: account.stale,
4386
- model: d.provider === badgeSelection?.provider ? badgeSelection.model : void 0,
4387
- provider: d.provider,
4388
- translate
4389
- }, `${d.provider}:${badgeSelection?.model ?? ""}`)]
4390
- }, account.key))]
4391
- }, d.provider))
4392
- ]
4393
- }), document.body)]
5090
+ }, `${d.provider}:${dialogModel ?? ""}`)]
5091
+ }, account.key))]
5092
+ }, d.provider))
5093
+ ]
5094
+ }), document.body)
5095
+ ]
4394
5096
  });
4395
5097
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [seat, statsRow !== null && statsRow.isConnected ? (0, react_dom.createPortal)(pill, statsRow) : pill] });
4396
5098
  }
@@ -4490,13 +5192,13 @@ const styles = {
4490
5192
  maxWidth: "100%",
4491
5193
  color: "var(--dsw-alias-label-tertiary)",
4492
5194
  font: "inherit",
4493
- fontSize: "var(--dsh-content-font-size-secondary, 13px)",
5195
+ fontSize: "calc(var(--dsh-content-font-size-secondary, 13px) - 1px)",
4494
5196
  fontVariantNumeric: "tabular-nums",
4495
- lineHeight: "20px",
5197
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
4496
5198
  whiteSpace: "nowrap",
4497
5199
  background: "transparent",
4498
5200
  border: "none",
4499
- borderRadius: 24,
5201
+ borderRadius: 999,
4500
5202
  alignItems: "center",
4501
5203
  gap: 6,
4502
5204
  padding: "1px 8px",
@@ -4528,7 +5230,7 @@ const styles = {
4528
5230
  color: "var(--dsw-alias-label-secondary)",
4529
5231
  cursor: "default",
4530
5232
  border: 0,
4531
- borderRadius: 12,
5233
+ borderRadius: "var(--dsw-radius-lg, 16px)",
4532
5234
  padding: 16,
4533
5235
  fontSize: 12,
4534
5236
  lineHeight: "18px"