dsh-ds-balance 2.1.3 → 2.3.0

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 (50) hide show
  1. package/README.md +71 -18
  2. package/README_en.md +76 -18
  3. package/lib/adapters/domain-core-store.d.ts +46 -2
  4. package/lib/adapters/domain-core-store.js +144 -4
  5. package/lib/adapters/host-account.d.ts +66 -0
  6. package/lib/adapters/host-account.js +68 -0
  7. package/lib/client.js +437 -95
  8. package/lib/domain/account.d.ts +40 -0
  9. package/lib/domain/account.js +74 -0
  10. package/lib/domain/balance.d.ts +40 -1
  11. package/lib/domain/balance.js +20 -1
  12. package/lib/domain/normalize.d.ts +3 -2
  13. package/lib/domain/normalize.js +3 -2
  14. package/lib/domain/severity.d.ts +8 -2
  15. package/lib/domain/severity.js +11 -4
  16. package/lib/domain/situation.d.ts +104 -0
  17. package/lib/domain/situation.js +93 -0
  18. package/lib/http/handlers.d.ts +1 -1
  19. package/lib/http/handlers.js +29 -5
  20. package/lib/http/wire.d.ts +11 -1
  21. package/lib/http/wire.js +2 -0
  22. package/lib/index.js +122 -6
  23. package/lib/ports/account.d.ts +27 -0
  24. package/lib/ports/account.js +9 -0
  25. package/lib/ports/core-store.d.ts +15 -0
  26. package/lib/services/balance-service.d.ts +66 -94
  27. package/lib/services/balance-service.js +132 -196
  28. package/lib/services/balance-source.d.ts +187 -0
  29. package/lib/services/balance-source.js +320 -0
  30. package/lib/services/scheduler.d.ts +8 -2
  31. package/lib/services/scheduler.js +1 -1
  32. package/lib/services/source-readers.d.ts +28 -0
  33. package/lib/services/source-readers.js +83 -0
  34. package/lib/services/source-selector.d.ts +50 -0
  35. package/lib/services/source-selector.js +64 -0
  36. package/lib/types/client/api-types.d.ts +16 -0
  37. package/lib/types/client/data.d.ts +12 -3
  38. package/lib/types/client/index.d.ts +6 -0
  39. package/lib/types/client/locales.d.ts +10 -18
  40. package/lib/types/client/mock/index.d.ts +12 -11
  41. package/lib/types/client/mock/scenarios.d.ts +13 -5
  42. package/lib/types/client/model.d.ts +24 -25
  43. package/lib/types/client/route-hint.d.ts +61 -0
  44. package/lib/types/client/sidebar/BalancePopover.d.ts +7 -0
  45. package/lib/types/client/sidebar/PercentRing.d.ts +13 -5
  46. package/lib/types/client/sidebar/SidebarBalance.d.ts +9 -1
  47. package/lib/types/client/situation.d.ts +164 -0
  48. package/lib/version.d.ts +1 -1
  49. package/lib/version.js +1 -1
  50. package/package.json +25 -23
package/lib/client.js CHANGED
@@ -42,11 +42,14 @@ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primi
42
42
  var NS = "ds-balance";
43
43
  var zh = {
44
44
  "sidebar.label": "DeepSeek \u4F59\u989D",
45
- "sidebar.aria.balance": "DeepSeek \u4F59\u989D",
46
45
  "sidebar.aria.ring": "DeepSeek \u4F59\u989D\u72B6\u6001",
47
46
  "sidebar.aria.refresh": "\u5237\u65B0\u4F59\u989D",
48
47
  "sidebar.aria.mismatch": "\u663E\u793A\u7684\u5E01\u79CD\u4E0E\u4F60\u9009\u62E9\u7684\u4E0D\u4E00\u81F4",
49
48
  "popover.title": "\u8D26\u6237\u4F59\u989D",
49
+ // 来源标签:跟着数据来源走。**括号属于词典** —— 中文全角、英文半角带前置空格,
50
+ // 代码里只做拼接,绝不写死括号字符。
51
+ "popover.source.key": "\uFF08API Key\uFF09",
52
+ "popover.source.account": "\uFF08\u8D26\u53F7\u767B\u5F55\uFF09",
50
53
  "popover.total": "\u4F59\u989D",
51
54
  "popover.granted": "\u8D60\u9001",
52
55
  "popover.toppedUp": "\u5145\u503C",
@@ -60,12 +63,33 @@ var zh = {
60
63
  // 措辞就跟着落点分档:一句含糊的话盖不住两种落点。见 BalancePopover 的 PluginsAction。
61
64
  "popover.action.openPlugins": "\u6253\u5F00\u63D2\u4EF6\u9875",
62
65
  "popover.action.openPluginConfig": "\u6253\u5F00\u63D2\u4EF6\u914D\u7F6E\u9875",
63
- "state.unavailable": "\u8D26\u6237\u4E0D\u53EF\u7528",
64
- "state.stale": "\u6570\u636E\u5DF2\u8FC7\u671F",
65
- "state.error": "\u8BFB\u53D6\u5931\u8D25",
66
- "state.empty": "\u5C1A\u672A\u914D\u7F6E",
67
- "state.noKey": "\u5C1A\u672A\u586B\u5199 API Key",
68
- "state.noBalance": "\u6682\u65E0\u4F59\u989D",
66
+ // 处境文案:**一个处境一句**,环 / 悬停 / 浮层都从这里取。
67
+ // 键名前缀是 situation 而不是 state:后者是契约字段名,而「处境」是界面概念
68
+ // (一个处境可能吃掉多个 state + severity + error 的组合)。
69
+ //
70
+ // **措辞是给用户看的,不是给开发者看的**。这七句会出现在两处,都必须能**单独读懂**:
71
+ // - **悬停气泡**(收起态与展开态同一份,见 `client/situation.ts` 的 `hoverTextOf`):
72
+ // 数字**可信**时气泡显示金额本身(只有 `ok` / `low` / `critical` 三个处境,
73
+ // 它们的 `textKey` 是 `null`);其余八个处境显示下面这些句子 —— 包括
74
+ // `stale` 与 `account-unavailable`,它们**手里有数字也显文案**;
75
+ // - 浮层与读屏的 `aria-label`。
76
+ //
77
+ // 所以每一句都要能单独回答「**这一格现在怎么了**」:
78
+ //
79
+ // - 不写「插件内部错误」:用户不知道也不关心是哪个组件坏了,他要的是「现在拿不到」。
80
+ // 改「服务异常」—— 说的是同一件事,但主语是这个功能,不是我们的代码。
81
+ // - `unavailable` 刻意**不带「暂时」**。它接住的是 `fetch-failed` 那一整片错误码,
82
+ // 里面既有等一等就好的(网络 / 超时 / 429 / 5xx),也有**等不好的**:
83
+ // `UPSTREAM_401`(key 失效)与 `UPSTREAM_402`(欠费)。对后者说「暂时」是假话,
84
+ // 而且是有害的假话 —— 用户该去改凭据、去充值,不是在原地等。
85
+ // 宁可少说一层时间性,也不能说反。
86
+ "situation.checking": "\u6B63\u5728\u83B7\u53D6",
87
+ "situation.unavailable": "\u8BFB\u4E0D\u5230\u4F59\u989D",
88
+ "situation.internalError": "\u670D\u52A1\u5F02\u5E38",
89
+ "situation.noCredential": "\u5C1A\u672A\u914D\u7F6E\u51ED\u636E",
90
+ "situation.accountUnavailable": "\u8D26\u6237\u4E0D\u53EF\u7528",
91
+ "situation.stale": "\u6570\u636E\u5DF2\u8FC7\u671F",
92
+ "situation.emptyWallet": "\u6682\u65E0\u4F59\u989D",
69
93
  "settings.group.connection": "\u8FDE\u63A5",
70
94
  "settings.group.refresh": "\u5237\u65B0",
71
95
  "settings.group.display": "\u5C55\u793A",
@@ -81,15 +105,13 @@ var zh = {
81
105
  "settings.field.cnyCritical": "CNY \u544A\u6025",
82
106
  "settings.field.usdWarn": "USD \u9884\u8B66",
83
107
  "settings.field.usdCritical": "USD \u544A\u6025",
84
- "settings.hint.apiKey": "\u7559\u7A7A\u5219\u6539\u7528\u5F15\u7528\u540D\u4ECE\u51ED\u636E\u5B58\u50A8\u8BFB\u53D6\u3002",
85
108
  "settings.hint.apiKeyRef": "\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u6761\u76EE\u540D\uFF1B\u975E\u7A7A\u65F6\u4F18\u5148\u4E8E API Key\u3002",
86
109
  "settings.hint.baseUrl": "\u7559\u7A7A\u5219\u4F7F\u7528\u63D0\u4F9B\u65B9\u9ED8\u8BA4\u5730\u5740\u3002",
87
110
  "settings.hint.displayCurrency": "\u6240\u9009\u5E01\u79CD\u4E0D\u5B58\u5728\u65F6\uFF0C\u56DE\u843D\u5230\u8D26\u6237\u5B9E\u9645\u6301\u6709\u7684\u5E01\u79CD\u3002",
88
- "settings.hint.number": "\u8BF7\u586B\u6570\u5B57\uFF1B\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u9ED8\u8BA4\u503C\u3002",
89
111
  "settings.hint.threshold": "\u540E\u7AEF\u5224\u5B9A\uFF1B\u754C\u9762\u989C\u8272\u8DDF\u968F\u8FD4\u56DE\u7684\u4E25\u91CD\u5EA6\u3002",
90
112
  "settings.hint.thresholdPair": "\u544A\u6025\u503C\u5FC5\u987B\u4F4E\u4E8E\u9884\u8B66\u503C",
91
113
  "settings.hint.serverRefreshSeconds": "\u8BBF\u95EE\u5B98\u65B9\u63A5\u53E3\u7684\u95F4\u9694\u3002",
92
- "settings.hint.clientPollSeconds": "\u5237\u65B0\u663E\u793A\u7684\u95F4\u9694\uFF1B\u53EA\u8BFB\u672C\u5730\u7F13\u5B58\uFF0C\u4E0D\u8BBF\u95EE\u4E0A\u6E38\u3002",
114
+ "settings.hint.clientPollSeconds": "\u5237\u65B0\u663E\u793A\u7684\u95F4\u9694\uFF1B\u6B63\u5E38\u65F6\u53EA\u8BFB\u7F13\u5B58\uFF0C\u8BFB\u4E0D\u5230\u65F6\u624D\u8BBF\u95EE\u4E0A\u6E38\u3002",
93
115
  "settings.hint.manualRefreshCooldownSeconds": "\u4E24\u6B21\u624B\u52A8\u5237\u65B0\u4E4B\u95F4\u7684\u6700\u77ED\u95F4\u9694\u3002",
94
116
  "settings.currency.auto": "\u81EA\u52A8\uFF08\u8DDF\u968F\u8D26\u6237\uFF09",
95
117
  // 凭据行的**状态徽章**:两条逐字用官方措辞 —— 取自 ui-settings-plugins 的
@@ -115,21 +137,24 @@ var zh = {
115
137
  "settings.overridden": "\u5DF2\u8986\u76D6",
116
138
  "settings.reset": "\u91CD\u7F6E",
117
139
  "settings.invalidNumber": "\u8BF7\u586B\u6570\u5B57\uFF1B\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u9ED8\u8BA4\u503C\u3002",
118
- "settings.hint.refreshAdvanced": "\u4E0D\u5E38\u6539\uFF1A\u9ED8\u8BA4\u503C\u9002\u7528\u4E8E\u5927\u591A\u6570\u60C5\u51B5\u3002",
119
- "settings.currencyMismatch": "\u5F53\u524D\u8D26\u6237\u65E0 {wanted} \u4F59\u989D\uFF0C\u5B9E\u9645\u663E\u793A {shown}",
140
+ "settings.hint.refreshAdvanced": "\u4E0D\u5E38\u6539\uFF1A\u9ED8\u8BA4\u503C\u9002\u7528\u4E8E\u5927\u591A\u6570\u60C5\u51B5\u3002"
120
141
  // 这里曾有一条 'settings.summary'(该行 Configure 子页的说明行)。回退到
121
142
  // plugins.bundle.config 之后没有任何渲染路径会问 summary 档,于是连同它一起删了 ——
122
143
  // 详见 src/client/index.tsx 的 SettingsSeat。
123
- "dev.title": "\u5F00\u53D1\u573A\u666F",
124
- "dev.hint": "\u4EC5\u7528\u4E8E UI \u5F00\u53D1\uFF1B\u4E0D\u5F71\u54CD\u771F\u5B9E\u914D\u7F6E\u3002"
144
+ // 本轮又删掉四条**孤儿键**(词典里有、全仓没人引用):`sidebar.aria.balance`
145
+ // (与 sidebar.label 逐字相同,rail 的 aria 走 aria.ring)、`settings.hint.apiKey`
146
+ // (那一行用的是 settings.hint.credential)、`settings.hint.number`
147
+ // (真正用的是 settings.invalidNumber,两者文案原本就重复)、
148
+ // `settings.currencyMismatch`(出到屏幕的是 popover.mismatch)。
125
149
  };
126
150
  var en = {
127
151
  "sidebar.label": "DeepSeek balance",
128
- "sidebar.aria.balance": "DeepSeek balance",
129
152
  "sidebar.aria.ring": "DeepSeek balance status",
130
153
  "sidebar.aria.refresh": "Refresh balance",
131
154
  "sidebar.aria.mismatch": "Shown currency differs from your selection",
132
155
  "popover.title": "Account balance",
156
+ "popover.source.key": " (API key)",
157
+ "popover.source.account": " (account sign-in)",
133
158
  "popover.total": "Balance",
134
159
  "popover.granted": "Granted",
135
160
  "popover.toppedUp": "Topped up",
@@ -141,12 +166,16 @@ var en = {
141
166
  "popover.action.useShown": "Use {shown}",
142
167
  "popover.action.openPlugins": "Open the Plugins page",
143
168
  "popover.action.openPluginConfig": "Open the plugin configuration page",
144
- "state.unavailable": "Account unavailable",
145
- "state.stale": "Data is stale",
146
- "state.error": "Failed to load",
147
- "state.empty": "Not configured",
148
- "state.noKey": "API key missing",
149
- "state.noBalance": "No balance",
169
+ // 与 zh 逐条对应,理由写在 zh 那一段。
170
+ // `unavailable` 同样**不带 Temporarily**:它接住 401(key 失效)/ 402(欠费)这类
171
+ // 「等不好」的错误,说「暂时」会把人劝去干等。
172
+ "situation.checking": "Loading",
173
+ "situation.unavailable": "Balance unavailable",
174
+ "situation.internalError": "Service error",
175
+ "situation.noCredential": "No credential configured",
176
+ "situation.accountUnavailable": "Account unavailable",
177
+ "situation.stale": "Data is stale",
178
+ "situation.emptyWallet": "No balance",
150
179
  "settings.group.connection": "Connection",
151
180
  "settings.group.refresh": "Refresh",
152
181
  "settings.group.display": "Display",
@@ -162,15 +191,13 @@ var en = {
162
191
  "settings.field.cnyCritical": "CNY critical",
163
192
  "settings.field.usdWarn": "USD warning",
164
193
  "settings.field.usdCritical": "USD critical",
165
- "settings.hint.apiKey": "Leave empty to resolve from the credential reference instead.",
166
194
  "settings.hint.apiKeyRef": "Credential store entry name; takes precedence over the API key.",
167
195
  "settings.hint.baseUrl": "Leave blank to use the provider default.",
168
196
  "settings.hint.displayCurrency": "Falls back to a currency the account actually holds.",
169
- "settings.hint.number": "Enter a number; leave empty to use the default.",
170
197
  "settings.hint.threshold": "Decided on the server; the UI colour follows the returned severity.",
171
198
  "settings.hint.thresholdPair": "Critical must be lower than warning",
172
199
  "settings.hint.serverRefreshSeconds": "How often the official API is called.",
173
- "settings.hint.clientPollSeconds": "How often the display refreshes; local cache only.",
200
+ "settings.hint.clientPollSeconds": "How often the display refreshes; cache-only while healthy.",
174
201
  "settings.hint.manualRefreshCooldownSeconds": "Minimum gap between two manual refreshes.",
175
202
  "settings.currency.auto": "Automatic (follow account)",
176
203
  "settings.credential.configured": "A key is configured.",
@@ -192,10 +219,8 @@ var en = {
192
219
  "settings.overridden": "Overridden",
193
220
  "settings.reset": "Reset",
194
221
  "settings.invalidNumber": "Enter a number; leave empty to use the default.",
195
- "settings.hint.refreshAdvanced": "Rarely changed: the defaults suit most setups.",
196
- "settings.currencyMismatch": "The account has no {wanted} balance; showing {shown}",
197
- "dev.title": "Dev scenario",
198
- "dev.hint": "UI development only; does not affect real configuration."
222
+ "settings.hint.refreshAdvanced": "Rarely changed: the defaults suit most setups."
223
+ // 与 zh 同批删掉四条孤儿键,以及 `dev.*`(见 zh 那段末尾的说明)。
199
224
  };
200
225
  function interpolate(template, params) {
201
226
  return template.replace(
@@ -853,6 +878,9 @@ function pendingView() {
853
878
  return {
854
879
  requestId: "",
855
880
  schemaVersion: 1,
881
+ // 占位视图没有取数路可言;处境为 checking / offline 时界面都不渲染来源标签。
882
+ source: "deepseek-http",
883
+ situation: "checking",
856
884
  state: "empty",
857
885
  stale: false,
858
886
  fetchedAt: 0,
@@ -870,6 +898,7 @@ function pendingView() {
870
898
  function unreachableView(message) {
871
899
  return {
872
900
  ...pendingView(),
901
+ situation: "offline",
873
902
  state: "error",
874
903
  error: { code: UNREACHABLE_CODE, message }
875
904
  };
@@ -884,7 +913,11 @@ async function readJson(response, path) {
884
913
  }
885
914
  async function requestBalance(options) {
886
915
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
887
- const url = `${BALANCE_PATH}?currency=${encodeURIComponent(options.currency)}`;
916
+ const query = new URLSearchParams({ currency: options.currency });
917
+ if (options.provider !== void 0 && options.provider !== "") {
918
+ query.set("provider", options.provider);
919
+ }
920
+ const url = `${BALANCE_PATH}?${query.toString()}`;
888
921
  const response = await fetchImpl(url, {
889
922
  headers: { accept: "application/json" },
890
923
  ...options.signal === void 0 ? {} : { signal: options.signal }
@@ -913,7 +946,10 @@ async function requestRefresh(options = {}) {
913
946
  const response = await fetchImpl(REFRESH_PATH, {
914
947
  method: "POST",
915
948
  headers: { "content-type": "application/json", accept: "application/json" },
916
- body: JSON.stringify({ reason: options.reason ?? "manual" })
949
+ body: JSON.stringify({
950
+ reason: options.reason ?? "manual",
951
+ ...options.provider === void 0 || options.provider === "" ? {} : { provider: options.provider }
952
+ })
917
953
  });
918
954
  return await readJson(response, REFRESH_PATH);
919
955
  }
@@ -1283,12 +1319,6 @@ function dotStateOf(severity) {
1283
1319
  return "idle";
1284
1320
  }
1285
1321
  }
1286
- function ringSpecOf(severity) {
1287
- return {
1288
- state: dotStateOf(severity),
1289
- marker: severity === "unavailable" ? "cross" : null
1290
- };
1291
- }
1292
1322
  var DECIMAL_SCALE = 100000000n;
1293
1323
  var ARC_STEPS = 10000n;
1294
1324
  var DECIMAL_PATTERN = /^(-?)(\d+)(?:\.(\d*))?$/;
@@ -1369,6 +1399,11 @@ function currentAgeMs(seenAt, seenAgeMs, now) {
1369
1399
  return Math.max(0, base + (now - seenAt));
1370
1400
  }
1371
1401
  var MS_PER_SECOND = 1e3;
1402
+ function sourceLabelKeyOf(source) {
1403
+ if (source === "deepseek-http") return "popover.source.key";
1404
+ if (source === "deepseek-account") return "popover.source.account";
1405
+ return null;
1406
+ }
1372
1407
  function cooldownUntilOf(now, configuredSeconds, rejectedRemainingMs) {
1373
1408
  const span = rejectedRemainingMs === null ? Math.max(0, configuredSeconds) * MS_PER_SECOND : Math.max(0, rejectedRemainingMs);
1374
1409
  return now + span;
@@ -1377,6 +1412,153 @@ function cooldownSecondsOf(until, now) {
1377
1412
  return Math.max(0, Math.ceil((until - now) / MS_PER_SECOND));
1378
1413
  }
1379
1414
 
1415
+ // src/client/situation.ts
1416
+ var SITUATIONS = [
1417
+ "checking",
1418
+ "offline",
1419
+ "internal-error",
1420
+ "no-credential",
1421
+ "fetch-failed",
1422
+ "account-unavailable",
1423
+ "stale",
1424
+ "empty-wallet",
1425
+ "ok",
1426
+ "low",
1427
+ "critical"
1428
+ ];
1429
+ var SHAPES = {
1430
+ // —— 客户端本地事实(宿主不可能知道这两件事)——
1431
+ checking: {
1432
+ family: "pending",
1433
+ ring: "ongoing",
1434
+ marker: null,
1435
+ arc: "none",
1436
+ textKey: "situation.checking",
1437
+ severityColoured: false
1438
+ },
1439
+ offline: {
1440
+ family: "unreadable",
1441
+ ring: "error",
1442
+ marker: "cross",
1443
+ arc: "none",
1444
+ textKey: "situation.unavailable",
1445
+ severityColoured: false
1446
+ },
1447
+ // —— 宿主判定 ——
1448
+ "internal-error": {
1449
+ family: "unreadable",
1450
+ ring: "error",
1451
+ marker: "cross",
1452
+ arc: "none",
1453
+ textKey: "situation.internalError",
1454
+ severityColoured: false
1455
+ },
1456
+ "no-credential": {
1457
+ family: "needs-credential",
1458
+ ring: "idle",
1459
+ marker: "plus",
1460
+ arc: "none",
1461
+ textKey: "situation.noCredential",
1462
+ severityColoured: false
1463
+ },
1464
+ "fetch-failed": {
1465
+ family: "unreadable",
1466
+ ring: "error",
1467
+ marker: "cross",
1468
+ arc: "none",
1469
+ textKey: "situation.unavailable",
1470
+ severityColoured: false
1471
+ },
1472
+ "account-unavailable": {
1473
+ family: "unreadable",
1474
+ // **颜色定死为红,不吃 severity。**
1475
+ // 它是「账户停用」—— 这个事实本身就决定了红,与余额多少无关;
1476
+ // 而上游对欠费账户可能给空的余额列表(selected=null → severity=unknown → 灰环),
1477
+ // 那会把「停用」画成「没信息」。定死红环是**结构保证**,不依赖另一处的判定顺序。
1478
+ ring: "error",
1479
+ // **叉:与「余额告急」分开的唯一办法。**
1480
+ // 从前它靠「红环无叉」与 critical 区分,但余额恰好为 0 的 critical 也是红环无叉无弧 ——
1481
+ // 两者在屏幕上逐像素相同(见 test/render-matrix.test.ts 的「两枚红环」一组)。
1482
+ // 叉的语义从「读不到」扩到「读不到**或用不了**」:两者同属「这里没有可用的数字」。
1483
+ marker: "cross",
1484
+ arc: "none",
1485
+ textKey: "situation.accountUnavailable",
1486
+ severityColoured: false
1487
+ },
1488
+ stale: {
1489
+ family: "gauge",
1490
+ ring: "done",
1491
+ marker: null,
1492
+ arc: "gauge",
1493
+ textKey: "situation.stale",
1494
+ severityColoured: true
1495
+ },
1496
+ "empty-wallet": {
1497
+ family: "empty",
1498
+ ring: "idle",
1499
+ marker: null,
1500
+ arc: "none",
1501
+ textKey: "situation.emptyWallet",
1502
+ severityColoured: false
1503
+ },
1504
+ ok: {
1505
+ family: "gauge",
1506
+ ring: "done",
1507
+ marker: null,
1508
+ arc: "gauge",
1509
+ textKey: null,
1510
+ severityColoured: true
1511
+ },
1512
+ low: {
1513
+ family: "gauge",
1514
+ ring: "warning",
1515
+ marker: null,
1516
+ arc: "gauge",
1517
+ textKey: null,
1518
+ severityColoured: true
1519
+ },
1520
+ critical: {
1521
+ family: "gauge",
1522
+ ring: "error",
1523
+ marker: null,
1524
+ arc: "gauge",
1525
+ textKey: null,
1526
+ severityColoured: true
1527
+ }
1528
+ };
1529
+ function presentationOf(situation, severity) {
1530
+ const shape = SHAPES[situation];
1531
+ if (!shape.severityColoured) return shape;
1532
+ return { ...shape, ring: dotStateOf(severity) };
1533
+ }
1534
+ function hoverTextOf(shown, stateText) {
1535
+ if (stateText !== null) return stateText;
1536
+ return shown === null ? null : formatMoney(shown.total, shown.currency);
1537
+ }
1538
+ function isSituation(value) {
1539
+ return typeof value === "string" && SITUATIONS.includes(value);
1540
+ }
1541
+ function legacySituationOf(response) {
1542
+ const hasSnapshot = response.fetchedAt > 0 || response.selected !== null;
1543
+ const code = response.error?.code;
1544
+ if (!hasSnapshot) {
1545
+ if (code === "NO_KEY") return "no-credential";
1546
+ if (code === "PLUGIN_UNREACHABLE") return "offline";
1547
+ return response.state === "empty" && code === void 0 ? "checking" : "fetch-failed";
1548
+ }
1549
+ if (!response.isAvailable) return "account-unavailable";
1550
+ if (response.selected === null)
1551
+ return response.state === "stale" ? "fetch-failed" : "empty-wallet";
1552
+ if (response.state === "stale") return "stale";
1553
+ if (response.severity === "critical") return "critical";
1554
+ if (response.severity === "warn") return "low";
1555
+ if (response.severity === "unavailable" || response.severity === "unknown") return "fetch-failed";
1556
+ return "ok";
1557
+ }
1558
+ function situationOfResponse(response) {
1559
+ return isSituation(response.situation) ? response.situation : legacySituationOf(response);
1560
+ }
1561
+
1380
1562
  // src/client/mock/scenarios.ts
1381
1563
  var T0 = 176e10;
1382
1564
  var cny = (total, granted, toppedUp) => ({
@@ -1389,6 +1571,8 @@ function make(patch) {
1389
1571
  return {
1390
1572
  requestId: "req_mock",
1391
1573
  schemaVersion: 1,
1574
+ source: "deepseek-http",
1575
+ situation: "ok",
1392
1576
  state: "ok",
1393
1577
  stale: false,
1394
1578
  fetchedAt: T0,
@@ -1407,27 +1591,33 @@ function make(patch) {
1407
1591
  var scenarios = {
1408
1592
  /** 正常。 */
1409
1593
  ok: make({}),
1594
+ /** 正常,但数字来自账号登录那条路(用来眼看「来源标签」)。 */
1595
+ account: make({ source: "deepseek-account" }),
1410
1596
  /** 余额偏低但可用。 */
1411
1597
  warn: make({
1598
+ situation: "low",
1412
1599
  severity: "warn",
1413
1600
  balances: [cny("8.00000000", "0.00000000", "8.00000000")],
1414
1601
  selected: { currency: "CNY", total: "8.00000000" }
1415
1602
  }),
1416
1603
  /** 余额告急。 */
1417
1604
  critical: make({
1605
+ situation: "critical",
1418
1606
  severity: "critical",
1419
1607
  balances: [cny("3.00000000", "0.00000000", "3.00000000")],
1420
1608
  selected: { currency: "CNY", total: "3.00000000" }
1421
1609
  }),
1422
- /** 账户不可用(余额耗尽 / 欠费)。 */
1610
+ /** 账户不可用(余额耗尽 / 欠费)。读到了,但上游说这个账户停用。 */
1423
1611
  unavailable: make({
1612
+ situation: "account-unavailable",
1424
1613
  severity: "unavailable",
1425
1614
  isAvailable: false,
1426
1615
  balances: [cny("0.00000000", "0.00000000", "0.00000000")],
1427
1616
  selected: { currency: "CNY", total: "0.00000000" }
1428
1617
  }),
1429
- /** 有旧值但本次刷新失败。 */
1618
+ /** 有旧值但本次刷新失败:数字照常显示,文案说它旧。 */
1430
1619
  stale: make({
1620
+ situation: "stale",
1431
1621
  state: "stale",
1432
1622
  stale: true,
1433
1623
  severity: "unknown",
@@ -1435,8 +1625,9 @@ var scenarios = {
1435
1625
  fetchedAt: T0 - 36e5,
1436
1626
  error: { code: "NO_NETWORK", message: "fetch failed" }
1437
1627
  }),
1438
- /** 无值失败。 */
1628
+ /** 无值失败:接入了却读不到(没有旧快照)。环是叉。 */
1439
1629
  error: make({
1630
+ situation: "fetch-failed",
1440
1631
  state: "error",
1441
1632
  stale: false,
1442
1633
  severity: "unknown",
@@ -1446,8 +1637,33 @@ var scenarios = {
1446
1637
  ageMs: 0,
1447
1638
  error: { code: "UPSTREAM_5XX", message: "upstream returned 500" }
1448
1639
  }),
1449
- /** 未配置。 */
1640
+ /** 插件自己的端点不可达(旧契约里没有这个信号,靠客户端本地合成)。 */
1641
+ offline: make({
1642
+ situation: "offline",
1643
+ state: "error",
1644
+ stale: false,
1645
+ severity: "unknown",
1646
+ isAvailable: false,
1647
+ balances: [],
1648
+ selected: null,
1649
+ ageMs: 0,
1650
+ error: { code: UNREACHABLE_CODE, message: "balance endpoint responded 502" }
1651
+ }),
1652
+ /** 插件自己出错了(装配错误之类):与「上游坏了」分开说。 */
1653
+ internalError: make({
1654
+ situation: "internal-error",
1655
+ state: "error",
1656
+ stale: false,
1657
+ severity: "unknown",
1658
+ isAvailable: false,
1659
+ balances: [],
1660
+ selected: null,
1661
+ ageMs: 0,
1662
+ error: { code: "STORAGE_ERROR", message: "no ledger for source deepseek-account" }
1663
+ }),
1664
+ /** 未配置:尚无任何解释。 */
1450
1665
  empty: make({
1666
+ situation: "empty-wallet",
1451
1667
  state: "empty",
1452
1668
  severity: "unknown",
1453
1669
  isAvailable: false,
@@ -1455,8 +1671,9 @@ var scenarios = {
1455
1671
  selected: null,
1456
1672
  ageMs: 0
1457
1673
  }),
1458
- /** 未配置且带 NO_KEY 错误。 */
1674
+ /** 未配置且带 NO_KEY 错误 —— 需要用户去配置的那一族。 */
1459
1675
  noKey: make({
1676
+ situation: "no-credential",
1460
1677
  state: "empty",
1461
1678
  severity: "unknown",
1462
1679
  isAvailable: false,
@@ -1465,6 +1682,16 @@ var scenarios = {
1465
1682
  ageMs: 0,
1466
1683
  error: { code: "NO_KEY", message: "no API key configured" }
1467
1684
  }),
1685
+ /** 首帧:还没问到后端。 */
1686
+ checking: make({
1687
+ situation: "checking",
1688
+ state: "empty",
1689
+ severity: "unknown",
1690
+ isAvailable: false,
1691
+ balances: [],
1692
+ selected: null,
1693
+ ageMs: 0
1694
+ }),
1468
1695
  /** 多币种。`selected` 显式写出,不靠 `make()` 的默认值兜。 */
1469
1696
  multiCurrency: make({
1470
1697
  balances: [
@@ -1489,6 +1716,7 @@ var scenarios = {
1489
1716
  }),
1490
1717
  /** 账户完全没有余额。 */
1491
1718
  noBalanceAtAll: make({
1719
+ situation: "empty-wallet",
1492
1720
  severity: "unknown",
1493
1721
  isAvailable: true,
1494
1722
  balances: [],
@@ -1626,7 +1854,9 @@ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primi
1626
1854
  var BalancePopover_default = {
1627
1855
  panel: "BalancePopover_panel",
1628
1856
  title: "BalancePopover_title",
1857
+ titleMain: "BalancePopover_titleMain",
1629
1858
  titleLabel: "BalancePopover_titleLabel",
1859
+ sourceLabel: "BalancePopover_sourceLabel",
1630
1860
  iconButton: "BalancePopover_iconButton",
1631
1861
  titleRule: "BalancePopover_titleRule",
1632
1862
  details: "BalancePopover_details",
@@ -1669,6 +1899,7 @@ function BalancePopover(props) {
1669
1899
  selection,
1670
1900
  displayCurrency,
1671
1901
  ageMs,
1902
+ sourceLabel,
1672
1903
  refreshing,
1673
1904
  cooldownSeconds,
1674
1905
  configSlotWarning,
@@ -1706,19 +1937,22 @@ function BalancePopover(props) {
1706
1937
  "aria-label": t("popover.title"),
1707
1938
  children: [
1708
1939
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BalancePopover_default.title, children: [
1709
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1710
- "a",
1711
- {
1712
- className: BalancePopover_default.titleLabel,
1713
- href: "https://platform.deepseek.com/usage",
1714
- target: "_blank",
1715
- rel: "noopener noreferrer",
1716
- children: [
1717
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.FishLogo, { size: 14 }),
1718
- t("popover.title")
1719
- ]
1720
- }
1721
- ),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: BalancePopover_default.titleMain, children: [
1941
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1942
+ "a",
1943
+ {
1944
+ className: BalancePopover_default.titleLabel,
1945
+ href: "https://platform.deepseek.com/usage",
1946
+ target: "_blank",
1947
+ rel: "noopener noreferrer",
1948
+ children: [
1949
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.FishLogo, { size: 14 }),
1950
+ t("popover.title")
1951
+ ]
1952
+ }
1953
+ ),
1954
+ sourceLabel === null ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: BalancePopover_default.sourceLabel, children: sourceLabel })
1955
+ ] }),
1722
1956
  pluginsAction === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.Tooltip, { label: pluginsLabel, side: "top", delayMs: 500, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1723
1957
  "button",
1724
1958
  {
@@ -1780,7 +2014,9 @@ var PercentRing_default = {
1780
2014
  ring: "PercentRing_ring",
1781
2015
  track: "PercentRing_track",
1782
2016
  fill: "PercentRing_fill",
1783
- cross: "PercentRing_cross"
2017
+ marker: "PercentRing_marker",
2018
+ spin: "PercentRing_spin",
2019
+ "dsb-ring-spin": "PercentRing_dsb-ring-spin"
1784
2020
  };
1785
2021
 
1786
2022
  // src/client/sidebar/PercentRing.tsx
@@ -1791,10 +2027,13 @@ var RING_INSET = 1;
1791
2027
  var RADIUS = VIEW / 2 - RING_INSET - STROKE / 2;
1792
2028
  var CENTER = VIEW / 2;
1793
2029
  var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
1794
- var CROSS_ARM = (2 * RADIUS - STROKE) / 2 / Math.SQRT2;
2030
+ var INNER_RADIUS = RADIUS - STROKE / 2;
2031
+ var MARK_ARM = INNER_RADIUS / 2 / Math.SQRT2;
1795
2032
  function round3(value) {
1796
2033
  return Math.round(value * 1e3) / 1e3;
1797
2034
  }
2035
+ var SPIN_ARC_RATIO = 12 / (2 * Math.PI * 9.5);
2036
+ var SPIN_ARC = round3(CIRCUMFERENCE * SPIN_ARC_RATIO);
1798
2037
  function PercentRing({
1799
2038
  state,
1800
2039
  marker = null,
@@ -1816,37 +2055,55 @@ function PercentRing({
1816
2055
  children: [
1817
2056
  title === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: title }),
1818
2057
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { className: PercentRing_default.track, cx: CENTER, cy: CENTER, r: RADIUS }),
1819
- clamped === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2058
+ state === "ongoing" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("g", { className: PercentRing_default.spin, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1820
2059
  "circle",
1821
2060
  {
1822
2061
  className: PercentRing_default.fill,
1823
2062
  cx: CENTER,
1824
2063
  cy: CENTER,
1825
2064
  r: RADIUS,
1826
- strokeDasharray: `${round3(CIRCUMFERENCE * clamped)} ${round3(CIRCUMFERENCE)}`,
2065
+ strokeDasharray: `${SPIN_ARC} ${round3(CIRCUMFERENCE)}`,
1827
2066
  transform: `rotate(-90 ${CENTER} ${CENTER})`
1828
2067
  }
2068
+ ) }) : (
2069
+ /* dash 取弧长、gap 取整周长,于是只画出一条弧;rotate 让接缝落在 12 点而不是 3 点
2070
+ (中心跟着 VIEW 走,16 格时是 rotate(-90 8 8))。
2071
+ 比例为 0 时整条弧不画:dash 长度为 0 配上圆头线帽会在 12 点留下一个点。 */
2072
+ clamped === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2073
+ "circle",
2074
+ {
2075
+ className: PercentRing_default.fill,
2076
+ cx: CENTER,
2077
+ cy: CENTER,
2078
+ r: RADIUS,
2079
+ strokeDasharray: `${round3(CIRCUMFERENCE * clamped)} ${round3(CIRCUMFERENCE)}`,
2080
+ transform: `rotate(-90 ${CENTER} ${CENTER})`
2081
+ }
2082
+ )
1829
2083
  ),
1830
- marker === "cross" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { className: PercentRing_default.cross, children: [
2084
+ marker === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("g", { className: PercentRing_default.marker, children: marker === "cross" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1831
2085
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1832
2086
  "line",
1833
2087
  {
1834
- x1: CENTER - CROSS_ARM,
1835
- y1: CENTER - CROSS_ARM,
1836
- x2: CENTER + CROSS_ARM,
1837
- y2: CENTER + CROSS_ARM
2088
+ x1: CENTER - MARK_ARM,
2089
+ y1: CENTER - MARK_ARM,
2090
+ x2: CENTER + MARK_ARM,
2091
+ y2: CENTER + MARK_ARM
1838
2092
  }
1839
2093
  ),
1840
2094
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1841
2095
  "line",
1842
2096
  {
1843
- x1: CENTER + CROSS_ARM,
1844
- y1: CENTER - CROSS_ARM,
1845
- x2: CENTER - CROSS_ARM,
1846
- y2: CENTER + CROSS_ARM
2097
+ x1: CENTER + MARK_ARM,
2098
+ y1: CENTER - MARK_ARM,
2099
+ x2: CENTER - MARK_ARM,
2100
+ y2: CENTER + MARK_ARM
1847
2101
  }
1848
2102
  )
1849
- ] }) : null
2103
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2104
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: CENTER, y1: CENTER - MARK_ARM, x2: CENTER, y2: CENTER + MARK_ARM }),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: CENTER - MARK_ARM, y1: CENTER, x2: CENTER + MARK_ARM, y2: CENTER })
2106
+ ] }) })
1850
2107
  ]
1851
2108
  }
1852
2109
  );
@@ -1893,19 +2150,22 @@ function usePluginsNavigation(navigation) {
1893
2150
  }, [navigation]);
1894
2151
  return action;
1895
2152
  }
1896
- function stateLabelKey(response, selection) {
1897
- if (response.state === "error")
1898
- return response.error?.code === "NO_KEY" ? "state.noKey" : "state.error";
1899
- if (response.state === "empty")
1900
- return response.error?.code === "NO_KEY" ? "state.noKey" : "state.empty";
1901
- if (response.state === "stale") return "state.stale";
1902
- if (!response.isAvailable) return "state.unavailable";
1903
- if (selection.shown === null) return "state.noBalance";
1904
- return null;
2153
+ function ringSpecFor(presentation) {
2154
+ return { state: presentation.ring, marker: presentation.marker };
1905
2155
  }
1906
- function ringSpecFor(severity) {
1907
- const spec = ringSpecOf(severity);
1908
- return { state: spec.state === "ongoing" ? "idle" : spec.state, marker: spec.marker };
2156
+ function useRouteProvider(hint) {
2157
+ const [provider, setProvider] = (0, import_react5.useState)(() => hint?.getSnapshot());
2158
+ (0, import_react5.useEffect)(() => {
2159
+ if (hint === void 0) {
2160
+ setProvider(void 0);
2161
+ return;
2162
+ }
2163
+ setProvider(hint.getSnapshot());
2164
+ return hint.subscribe(() => {
2165
+ setProvider(hint.getSnapshot());
2166
+ });
2167
+ }, [hint]);
2168
+ return provider;
1909
2169
  }
1910
2170
  function SidebarBalance({
1911
2171
  wide,
@@ -1913,7 +2173,8 @@ function SidebarBalance({
1913
2173
  config,
1914
2174
  configSlotProbe,
1915
2175
  onSelectCurrency,
1916
- pluginsNavigation
2176
+ pluginsNavigation,
2177
+ routeHint
1917
2178
  }) {
1918
2179
  const [view, setView] = (0, import_react5.useState)(() => {
1919
2180
  const response2 = resolveScenario() === null ? pendingView() : currentBalance();
@@ -1926,6 +2187,7 @@ function SidebarBalance({
1926
2187
  const [refreshing, setRefreshing] = (0, import_react5.useState)(false);
1927
2188
  const [cooldownUntil, setCooldownUntil] = (0, import_react5.useState)(0);
1928
2189
  const [now, setNow] = (0, import_react5.useState)(() => Date.now());
2190
+ const provider = useRouteProvider(routeHint);
1929
2191
  const rootRef = (0, import_react5.useRef)(null);
1930
2192
  const panelRef = (0, import_react5.useRef)(null);
1931
2193
  const refreshTimer = (0, import_react5.useRef)(void 0);
@@ -1960,7 +2222,7 @@ function SidebarBalance({
1960
2222
  let cancelled = false;
1961
2223
  const load = async () => {
1962
2224
  try {
1963
- const next = await requestBalance({ currency: preference });
2225
+ const next = await requestBalance({ currency: preference, provider });
1964
2226
  if (cancelled) return;
1965
2227
  loadedRef.current = true;
1966
2228
  acceptBalance(next);
@@ -1983,6 +2245,7 @@ function SidebarBalance({
1983
2245
  }, [
1984
2246
  mock,
1985
2247
  preference,
2248
+ provider,
1986
2249
  config.clientPollSeconds,
1987
2250
  config.configSignature,
1988
2251
  acceptBalance,
@@ -2066,9 +2329,9 @@ function SidebarBalance({
2066
2329
  void (async () => {
2067
2330
  let rejectedRemainingMs = null;
2068
2331
  try {
2069
- const result = await requestRefresh({ reason: "manual" });
2332
+ const result = await requestRefresh({ reason: "manual", provider });
2070
2333
  if (!result.triggered) rejectedRemainingMs = Math.max(0, result.cooldownMs);
2071
- const next = await requestBalance({ currency: preference });
2334
+ const next = await requestBalance({ currency: preference, provider });
2072
2335
  loadedRef.current = true;
2073
2336
  acceptBalance(next);
2074
2337
  } catch (error) {
@@ -2082,6 +2345,7 @@ function SidebarBalance({
2082
2345
  config.manualRefreshCooldownSeconds,
2083
2346
  mock,
2084
2347
  preference,
2348
+ provider,
2085
2349
  refreshing,
2086
2350
  acceptBalance,
2087
2351
  showUnreachable,
@@ -2105,18 +2369,20 @@ function SidebarBalance({
2105
2369
  );
2106
2370
  const configSlotState = useConfigSlotState(configSlotProbe);
2107
2371
  const shown = selection.shown;
2108
- const ring = ringSpecFor(response.severity);
2109
- const ringRatio = ringRatioOf(
2372
+ const situation = situationOfResponse(response);
2373
+ const presentation = presentationOf(situation, response.severity);
2374
+ const ring = ringSpecFor(presentation);
2375
+ const ringRatio = presentation.arc === "gauge" ? ringRatioOf(
2110
2376
  shown === null ? null : shown.total,
2111
2377
  shown === null ? void 0 : config.warnThresholdOf(shown.currency),
2112
2378
  response.severity
2113
- );
2114
- const stateKey = stateLabelKey(response, selection);
2379
+ ) : 0;
2380
+ const stateKey = presentation.textKey;
2115
2381
  const stateText = stateKey === null ? null : t(stateKey);
2116
2382
  let markerLabel = null;
2117
2383
  let markerHint = "";
2118
2384
  if (shown === null) {
2119
- markerLabel = t(stateKey ?? "state.noBalance");
2385
+ markerLabel = t(stateKey ?? "situation.emptyWallet");
2120
2386
  markerHint = markerLabel;
2121
2387
  } else if (!selection.matchesPreference) {
2122
2388
  markerLabel = t("sidebar.aria.mismatch");
@@ -2128,9 +2394,10 @@ function SidebarBalance({
2128
2394
  if (markerLabel !== null) ariaParts.push(markerLabel);
2129
2395
  const ariaLabel = ariaParts.join(" ");
2130
2396
  const cooldownSeconds = cooldownSecondsOf(cooldownUntil, now);
2397
+ const sourceKey = shown === null ? null : sourceLabelKeyOf(response.source);
2398
+ const sourceLabel = sourceKey === null ? null : t(sourceKey);
2131
2399
  const ageMs = currentAgeMs(view.seenAt, view.seenAgeMs, now);
2132
- const ringTitle = !wide && stateText !== null ? `${t("sidebar.aria.ring")} ${stateText}` : void 0;
2133
- const hoverLabel = shown === null ? stateText : formatMoney(shown.total, shown.currency);
2400
+ const hoverText = hoverTextOf(shown, stateText);
2134
2401
  const trigger = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2135
2402
  "button",
2136
2403
  {
@@ -2150,13 +2417,13 @@ function SidebarBalance({
2150
2417
  marker: ring.marker,
2151
2418
  ratio: ringRatio,
2152
2419
  size: 18,
2153
- title: ringTitle
2420
+ title: hoverText ?? void 0
2154
2421
  }
2155
2422
  )
2156
2423
  }
2157
2424
  );
2158
2425
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: rootRef, className: SidebarBalance_default.root, "data-mode": wide ? "wide" : "rail", tabIndex: -1, children: [
2159
- wide && hoverLabel !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.Tooltip, { label: hoverLabel, side: "right", delayMs: 500, disabled: open, children: trigger }) : trigger,
2426
+ wide && hoverText !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives4.Tooltip, { label: hoverText, side: "right", delayMs: 500, disabled: open, children: trigger }) : trigger,
2160
2427
  open ? (0, import_react_dom.createPortal)(
2161
2428
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2162
2429
  BalancePopover,
@@ -2165,6 +2432,7 @@ function SidebarBalance({
2165
2432
  selection,
2166
2433
  displayCurrency: preference,
2167
2434
  ageMs,
2435
+ sourceLabel,
2168
2436
  refreshing,
2169
2437
  cooldownSeconds,
2170
2438
  configSlotWarning: configSlotState === "missing" ? CONFIG_SLOT_WARNING : null,
@@ -2181,6 +2449,66 @@ function SidebarBalance({
2181
2449
  ] });
2182
2450
  }
2183
2451
 
2452
+ // src/client/route-hint.ts
2453
+ function safely(read) {
2454
+ try {
2455
+ return read();
2456
+ } catch {
2457
+ return void 0;
2458
+ }
2459
+ }
2460
+ function projectionOf(ctx) {
2461
+ return safely(() => {
2462
+ const current = ctx.uiSession?.adapter?.current?.getSnapshot?.();
2463
+ const key = current?.key;
2464
+ if (key === void 0) return void 0;
2465
+ return ctx.sessions?.binding?.(key)?.session?.projections?.faceOf?.("modelSelection");
2466
+ });
2467
+ }
2468
+ function createRouteHint(services) {
2469
+ const listeners2 = /* @__PURE__ */ new Set();
2470
+ let stopCurrent;
2471
+ let stopProjection;
2472
+ const notify = () => {
2473
+ for (const listener of [...listeners2]) listener();
2474
+ };
2475
+ const bindProjection = () => {
2476
+ stopProjection?.();
2477
+ stopProjection = safely(() => projectionOf(services())?.subscribe?.(notify));
2478
+ };
2479
+ const start = () => {
2480
+ stopCurrent = safely(
2481
+ () => services().uiSession?.adapter?.current?.subscribe?.(() => {
2482
+ bindProjection();
2483
+ notify();
2484
+ })
2485
+ );
2486
+ bindProjection();
2487
+ };
2488
+ const stop = () => {
2489
+ stopCurrent?.();
2490
+ stopProjection?.();
2491
+ stopCurrent = void 0;
2492
+ stopProjection = void 0;
2493
+ };
2494
+ return {
2495
+ getSnapshot() {
2496
+ const value = safely(
2497
+ () => projectionOf(services())?.getSnapshot?.()?.next?.provider
2498
+ );
2499
+ return typeof value === "string" && value !== "" ? value : void 0;
2500
+ },
2501
+ subscribe(listener) {
2502
+ listeners2.add(listener);
2503
+ if (listeners2.size === 1) start();
2504
+ return () => {
2505
+ listeners2.delete(listener);
2506
+ if (listeners2.size === 0) stop();
2507
+ };
2508
+ }
2509
+ };
2510
+ }
2511
+
2184
2512
  // src/client/index.tsx
2185
2513
  var import_jsx_runtime6 = require("react/jsx-runtime");
2186
2514
  var ENTRY_ID = "dsh-ds-balance";
@@ -2250,6 +2578,7 @@ function SidebarSeatComponent(props) {
2250
2578
  t: translateOf(props.seat.t),
2251
2579
  configSlotProbe: props.configSlotProbe,
2252
2580
  pluginsNavigation: props.pluginsNavigation,
2581
+ routeHint: props.routeHint,
2253
2582
  onSelectCurrency: selectCurrency,
2254
2583
  config: {
2255
2584
  displayCurrency: readDisplayCurrency(value),
@@ -2329,6 +2658,18 @@ function apply(ctx) {
2329
2658
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "ds-balance: dictionaries");
2330
2659
  const configSlotProbe = createConfigSlotProbe();
2331
2660
  ctx.effect(() => () => configSlotProbe.dispose(), "ds-balance: config slot probe");
2661
+ const routeServices = {};
2662
+ for (const name of ["uiSession", "sessions"]) {
2663
+ ctx.inject([name], (serviceCtx) => {
2664
+ serviceCtx.effect(() => {
2665
+ routeServices[name] = serviceCtx[name];
2666
+ return () => {
2667
+ delete routeServices[name];
2668
+ };
2669
+ }, `ds-balance: route hint (${name})`);
2670
+ });
2671
+ }
2672
+ const routeHint = createRouteHint(() => routeServices);
2332
2673
  const pluginsNavigation = createPluginsNavigation();
2333
2674
  ctx.inject(["pluginNavigation"], (navCtx) => {
2334
2675
  const face = navCtx["pluginNavigation"];
@@ -2374,14 +2715,15 @@ function apply(ctx) {
2374
2715
  seat,
2375
2716
  form,
2376
2717
  configSlotProbe,
2377
- pluginsNavigation
2718
+ pluginsNavigation,
2719
+ routeHint
2378
2720
  }
2379
2721
  )
2380
2722
  )
2381
2723
  );
2382
2724
  });
2383
2725
  }
2384
- var __dsbCss = "/* src/client/settings/fields.module.css */\n.fields_group {\n padding: 12px 0 0;\n}\n.fields_groupLast {\n padding-bottom: 12px;\n}\n.fields_groupNote {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_fields {\n display: flex;\n flex-direction: column;\n}\n.fields_fields > * + * {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.fields_details {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin-top: 12px;\n padding-top: 12px;\n}\n.fields_detailsBody {\n padding-top: 10px;\n}\n.fields_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.fields_row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n}\n.fields_rowText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-right: 48px;\n}\n.fields_rowTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_rowTitle {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_badges {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.fields_reset {\n border: none;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n.fields_reset:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reset:disabled {\n cursor: default;\n}\n.fields_inputStatic,\n.fields_inputStatic:hover {\n pointer-events: none;\n}\n.fields_inputStatic input,\n.fields_inputStatic input:hover,\n.fields_inputStatic input:focus,\n.fields_inputStatic input:focus-visible,\n.fields_inputStatic input:active {\n border-color: var(--dsw-alias-border-l4);\n background: var(--dsw-alias-bg-layer-3);\n outline: none;\n box-shadow: none;\n cursor: default;\n}\n.fields_inputInvalid input {\n border: 1px solid var(--dsw-alias-state-error-primary);\n}\n.fields_inputInvalid input:focus-visible {\n border-color: var(--dsw-alias-brand-primary);\n}\n.fields_inputWithAction {\n position: relative;\n display: flex;\n align-items: center;\n}\n.fields_inputWithAction .fields_input,\n.fields_inputWithAction .fields_inputInvalid {\n padding-right: 38px;\n}\n.fields_reveal {\n position: absolute;\n right: 4px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 999px;\n corner-shape: round;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.fields_reveal:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.fields_revealOn {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reveal:disabled {\n cursor: default;\n opacity: 0.4;\n}\n.fields_selectorAnchor {\n flex: none;\n}\n.fields_selector {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n flex: none;\n max-width: 100%;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 18px;\n background: var(--dsw-alias-bg-module-platform);\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n text-align: left;\n cursor: pointer;\n}\n.fields_selector:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.fields_selector:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.fields_selectorChevron {\n flex: none;\n}\n.fields_hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_pairNote {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n.fields_pairNote::before {\n content: \"\";\n flex: none;\n width: 5px;\n height: 5px;\n border-radius: 999px;\n corner-shape: round;\n background: currentColor;\n}\n.fields_invalid {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.fields_actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_resultOk {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-success-primary);\n}\n.fields_resultFail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* src/client/settings/BalanceSettingsCard.module.css */\n.BalanceSettingsCard_form {\n display: flex;\n flex-direction: column;\n}\n.BalanceSettingsCard_readOnly {\n margin: 0 0 12px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalanceSettingsCard_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 16px;\n}\n.BalanceSettingsCard_failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.BalanceSettingsCard_notice {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* src/client/sidebar/BalancePopover.module.css */\n.BalancePopover_panel {\n position: fixed;\n isolation: isolate;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 16px;\n border: 0;\n border-radius: 12px;\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n.BalancePopover_panel::before {\n position: absolute;\n z-index: -1;\n inset: 0;\n border-radius: inherit;\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n content: \"\";\n pointer-events: none;\n}\n.BalancePopover_title {\n display: flex;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n.BalancePopover_titleLabel {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n cursor: pointer;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.BalancePopover_titleLabel:hover,\n.BalancePopover_titleLabel:visited {\n color: inherit;\n}\n.BalancePopover_titleLabel svg {\n flex: none;\n width: 14px;\n height: auto;\n}\n.BalancePopover_iconButton {\n position: relative;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 50%;\n corner-shape: round;\n padding: 0;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_iconButton:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.BalancePopover_iconButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.BalancePopover_titleRule {\n margin-bottom: 10px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.BalancePopover_details {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 6px 16px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_details dt,\n.BalancePopover_details dd {\n min-width: 0;\n margin: 0;\n}\n.BalancePopover_details dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n.BalancePopover_notice {\n margin-top: 12px;\n}\n.BalancePopover_noticeText {\n margin: 0 0 8px;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_noticeActions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.BalancePopover_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 12px;\n}\n.BalancePopover_updated {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_status {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_refresh {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: none;\n border-radius: 999px;\n corner-shape: round;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.BalancePopover_refresh:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_refresh:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n.BalancePopover_refresh:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.BalancePopover_refresh[data-cooling] {\n color: var(--dsw-alias-label-caption);\n}\n.BalancePopover_refresh[data-refreshing] svg {\n animation: BalancePopover_balance-refresh-spin 900ms linear infinite;\n}\n@keyframes BalancePopover_balance-refresh-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .BalancePopover_refresh[data-refreshing] svg {\n animation-duration: 2400ms;\n }\n}\n\n/* src/client/sidebar/PercentRing.module.css */\n.PercentRing_ring {\n display: block;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.PercentRing_ring[data-state=done] {\n color: var(--dsw-alias-state-success-primary);\n}\n.PercentRing_ring[data-state=warning] {\n color: var(--dsw-alias-state-warn-primary);\n}\n.PercentRing_ring[data-state=error] {\n color: var(--dsw-alias-state-error-primary);\n}\n.PercentRing_ring[data-state=idle] {\n color: var(--dsw-alias-state-idle-primary);\n}\n.PercentRing_track {\n fill: none;\n stroke: var(--dsw-alias-border-l3);\n stroke-width: 1;\n}\n.PercentRing_fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_cross {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n\n/* src/client/sidebar/SidebarBalance.module.css */\n.SidebarBalance_root {\n position: relative;\n display: flex;\n min-width: 0;\n}\n.SidebarBalance_root[data-mode=wide] {\n flex: none;\n width: calc(100% + 4px);\n margin: 4px -2px 0;\n}\n.SidebarBalance_root[data-mode=rail] {\n flex: none;\n margin: 8px 0 0;\n}\n.SidebarBalance_trigger {\n display: flex;\n align-items: center;\n box-sizing: border-box;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n overflow: hidden;\n}\n.SidebarBalance_trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.SidebarBalance_trigger:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.SidebarBalance_root[data-mode=wide] .SidebarBalance_trigger {\n flex: none;\n gap: 8px;\n width: 100%;\n height: 42px;\n padding: 0 10px 0 8px;\n border-radius: 12px;\n}\n.SidebarBalance_root[data-mode=rail] .SidebarBalance_trigger {\n flex: none;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border-radius: 12px;\n}\n.SidebarBalance_icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n}\n.SidebarBalance_label {\n flex: 0 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.SidebarBalance_marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--dsw-alias-label-tertiary);\n position: relative;\n top: 1.63px;\n}\n\n/* src/client/sidebar/footer-stack.module.css */\ndiv:has(> [data-slot=\"sidebar.footer.action\"]) {\n flex-direction: column;\n}\n";
2726
+ var __dsbCss = "/* src/client/settings/fields.module.css */\n.fields_group {\n padding: 12px 0 0;\n}\n.fields_groupLast {\n padding-bottom: 12px;\n}\n.fields_groupNote {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_fields {\n display: flex;\n flex-direction: column;\n}\n.fields_fields > * + * {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.fields_details {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin-top: 12px;\n padding-top: 12px;\n}\n.fields_detailsBody {\n padding-top: 10px;\n}\n.fields_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.fields_row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n}\n.fields_rowText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-right: 48px;\n}\n.fields_rowTitleLine {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_rowTitle {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.fields_badges {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.fields_reset {\n border: none;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n.fields_reset:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reset:disabled {\n cursor: default;\n}\n.fields_inputStatic,\n.fields_inputStatic:hover {\n pointer-events: none;\n}\n.fields_inputStatic input,\n.fields_inputStatic input:hover,\n.fields_inputStatic input:focus,\n.fields_inputStatic input:focus-visible,\n.fields_inputStatic input:active {\n border-color: var(--dsw-alias-border-l4);\n background: var(--dsw-alias-bg-layer-3);\n outline: none;\n box-shadow: none;\n cursor: default;\n}\n.fields_inputInvalid input {\n border: 1px solid var(--dsw-alias-state-error-primary);\n}\n.fields_inputInvalid input:focus-visible {\n border-color: var(--dsw-alias-brand-primary);\n}\n.fields_inputWithAction {\n position: relative;\n display: flex;\n align-items: center;\n}\n.fields_inputWithAction .fields_input,\n.fields_inputWithAction .fields_inputInvalid {\n padding-right: 38px;\n}\n.fields_reveal {\n position: absolute;\n right: 4px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 50%;\n corner-shape: round;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.fields_reveal:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.fields_revealOn {\n color: var(--dsw-alias-label-primary);\n}\n.fields_reveal:disabled {\n cursor: default;\n opacity: 0.4;\n}\n.fields_selectorAnchor {\n flex: none;\n}\n.fields_selector {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n flex: none;\n max-width: 100%;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-module-platform);\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n text-align: left;\n cursor: pointer;\n}\n.fields_selector:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.fields_selector:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.fields_selectorChevron {\n flex: none;\n}\n.fields_hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.fields_pairNote {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n.fields_pairNote::before {\n content: \"\";\n flex: none;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n corner-shape: round;\n background: currentColor;\n}\n.fields_invalid {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.fields_actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.fields_resultOk {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-success-primary);\n}\n.fields_resultFail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* src/client/settings/BalanceSettingsCard.module.css */\n.BalanceSettingsCard_form {\n display: flex;\n flex-direction: column;\n}\n.BalanceSettingsCard_readOnly {\n margin: 0 0 12px;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalanceSettingsCard_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 16px;\n}\n.BalanceSettingsCard_failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary);\n}\n.BalanceSettingsCard_notice {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* src/client/sidebar/BalancePopover.module.css */\n.BalancePopover_panel {\n position: fixed;\n isolation: isolate;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 16px;\n border: 0;\n border-radius: var(--dsw-radius-lg);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n.BalancePopover_panel::before {\n position: absolute;\n z-index: -1;\n inset: 0;\n border-radius: inherit;\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n content: \"\";\n pointer-events: none;\n}\n.BalancePopover_title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n.BalancePopover_titleMain {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.BalancePopover_titleLabel {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n cursor: pointer;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.BalancePopover_titleLabel:hover,\n.BalancePopover_titleLabel:visited {\n color: inherit;\n}\n.BalancePopover_sourceLabel {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n font-weight: 400;\n}\n.BalancePopover_titleLabel svg {\n flex: none;\n width: 14px;\n height: auto;\n}\n.BalancePopover_iconButton {\n position: relative;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n margin-block: -5px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n padding: 0;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_iconButton:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.BalancePopover_iconButton:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.BalancePopover_titleRule {\n margin-bottom: 10px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.BalancePopover_details {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 6px 16px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_details dt,\n.BalancePopover_details dd {\n min-width: 0;\n margin: 0;\n}\n.BalancePopover_details dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n.BalancePopover_notice {\n margin-top: 12px;\n}\n.BalancePopover_noticeText {\n margin: 0 0 8px;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_noticeActions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.BalancePopover_footer {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 12px;\n}\n.BalancePopover_updated {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_status {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n}\n.BalancePopover_refresh {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.BalancePopover_refresh:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n.BalancePopover_refresh:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.BalancePopover_refresh:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.BalancePopover_refresh[data-cooling] {\n color: var(--dsw-alias-label-caption);\n}\n.BalancePopover_refresh[data-refreshing] svg {\n animation: BalancePopover_balance-refresh-spin 900ms linear infinite;\n}\n@keyframes BalancePopover_balance-refresh-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .BalancePopover_refresh[data-refreshing] svg {\n animation-duration: 2400ms;\n }\n}\n\n/* src/client/sidebar/PercentRing.module.css */\n.PercentRing_ring {\n display: block;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n.PercentRing_ring[data-state=done] {\n color: var(--dsw-alias-state-success-primary);\n}\n.PercentRing_ring[data-state=warning] {\n color: var(--dsw-alias-state-warn-primary);\n}\n.PercentRing_ring[data-state=error] {\n color: var(--dsw-alias-state-error-primary);\n}\n.PercentRing_ring[data-state=idle] {\n color: var(--dsw-alias-state-idle-primary);\n}\n.PercentRing_track {\n fill: none;\n stroke: var(--dsw-alias-border-l3);\n stroke-width: 1;\n}\n.PercentRing_fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_marker {\n fill: none;\n stroke: currentColor;\n stroke-width: 1;\n stroke-linecap: round;\n}\n.PercentRing_spin {\n transform-origin: center;\n animation: PercentRing_dsb-ring-spin 1.5s linear infinite;\n}\n@keyframes PercentRing_dsb-ring-spin {\n to {\n transform: rotate(360deg);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .PercentRing_spin {\n animation: none;\n }\n}\n\n/* src/client/sidebar/SidebarBalance.module.css */\n.SidebarBalance_root {\n position: relative;\n display: flex;\n min-width: 0;\n}\n.SidebarBalance_root[data-mode=wide] {\n flex: none;\n width: calc(100% + 4px);\n margin: 4px -2px 0;\n}\n.SidebarBalance_root[data-mode=rail] {\n flex: none;\n margin: 8px 0 0;\n}\n.SidebarBalance_trigger {\n display: flex;\n align-items: center;\n box-sizing: border-box;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n overflow: hidden;\n}\n.SidebarBalance_trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.SidebarBalance_trigger:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n.SidebarBalance_root[data-mode=wide] .SidebarBalance_trigger {\n flex: none;\n gap: 8px;\n width: 100%;\n height: 42px;\n padding: 0 10px 0 8px;\n border-radius: var(--dsw-radius-md);\n}\n.SidebarBalance_root[data-mode=rail] .SidebarBalance_trigger {\n flex: none;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border-radius: var(--dsw-radius-md);\n}\n.SidebarBalance_icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n}\n.SidebarBalance_label {\n flex: 0 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.SidebarBalance_marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--dsw-alias-label-tertiary);\n position: relative;\n top: 1.63px;\n}\n\n/* src/client/sidebar/footer-stack.module.css */\ndiv:has(> [data-slot=\"sidebar.footer.action\"]) {\n flex-direction: column;\n}\n";
2385
2727
  var __dsbTagId = "dsh-ds-balance/client.css";
2386
2728
  if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="' + __dsbTagId + '"]') === null) {
2387
2729
  var __dsbTag = document.createElement('style');