dsh-qoder-connect 0.1.2 → 0.1.5

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.
package/lib/client.js CHANGED
@@ -61,238 +61,711 @@ window.__ModuleLoader__.load({
61
61
  return status === "error" && typeof wrapped["message"] === "string";
62
62
  }
63
63
  //#endregion
64
- //#region src/client/QoderPluginCard.tsx
65
- /** Qoder status card contributed to Harness Plugin configuration. */
66
- /** China Qoder; the plugin's primary card and default. */
67
- const QODER_CN_CARD = {
68
- id: "qoder",
69
- titleKey: "title",
70
- introKey: "intro",
71
- signedOutKey: "signedOutHint",
72
- patGuideKey: "patGuide",
73
- patPlaceholderKey: "patPlaceholder",
74
- statusPath: QODER_STATUS_PATH,
75
- probePath: QODER_PROBE_PATH,
76
- authPath: QODER_AUTH_PATH
64
+ //#region src/client/quota-settings-store.ts
65
+ let pollIntervalMs = 3e5;
66
+ const toggles = {
67
+ cn: false,
68
+ global: false
77
69
  };
78
- /** Both cards, in display order (China first). */
79
- const QODER_CARD_VARIANTS = [QODER_CN_CARD, {
80
- id: "qoder-global",
81
- titleKey: "titleAI",
82
- introKey: "introAI",
83
- signedOutKey: "signedOutHintAI",
84
- patGuideKey: "patGuideAI",
85
- patPlaceholderKey: "patPlaceholderAI",
86
- statusPath: QODER_GLOBAL_STATUS_PATH,
87
- probePath: QODER_GLOBAL_PROBE_PATH,
88
- authPath: QODER_GLOBAL_AUTH_PATH
89
- }];
90
- const POLL_INTERVAL_MS = 6e4;
91
- const cardStyle$1 = {
92
- listStyle: "none",
93
- borderWidth: "0.5px",
94
- borderStyle: "solid",
95
- borderColor: "var(--dsw-alias-border-l4)",
96
- borderRadius: 16,
97
- background: "var(--dsw-alias-bg-layer-3)",
98
- transition: "border-color .16s, background .16s"
70
+ let togglesSnapshot = {
71
+ cn: false,
72
+ global: false
99
73
  };
100
- /** Hover, matching the built-in card's `:hover`. Inline styles cannot express a pseudo-class. */
101
- const cardHoverStyle$1 = { borderColor: "var(--dsw-alias-label-dimmed)" };
102
- /** Expanded, matching the built-in card's open state. */
103
- const cardOpenStyle$1 = {
104
- background: "var(--dsw-alias-bg-layer-2)",
105
- borderColor: "var(--dsw-alias-label-dimmed)"
74
+ const signIn = {
75
+ cn: false,
76
+ global: false
106
77
  };
107
- const headerStyle = {
108
- boxSizing: "border-box",
109
- width: "100%",
110
- display: "flex",
111
- alignItems: "center",
112
- gap: 12,
113
- border: 0,
114
- borderRadius: 12,
115
- padding: "14px 16px",
116
- background: "transparent",
117
- color: "inherit",
118
- font: "inherit",
119
- textAlign: "left",
120
- cursor: "pointer",
121
- appearance: "none"
78
+ let signInSnapshot = {
79
+ cn: false,
80
+ global: false
122
81
  };
82
+ let revision = 0;
83
+ const listeners = /* @__PURE__ */ new Set();
84
+ function bump() {
85
+ revision += 1;
86
+ for (const listener of listeners) listener();
87
+ }
88
+ /** Update the shared poll interval (from the settings document). */
89
+ function setQuotaPollMs(ms) {
90
+ if (Number.isFinite(ms) && ms >= 6e4 && pollIntervalMs !== ms) {
91
+ pollIntervalMs = ms;
92
+ bump();
93
+ }
94
+ }
95
+ /** Read the configured poll interval. */
96
+ function quotaPollMs() {
97
+ return pollIntervalMs;
98
+ }
99
+ /** Update both sidebar toggles (from the settings document). */
100
+ function setQuotaToggles(cn, global) {
101
+ if (toggles.cn !== cn || toggles.global !== global) {
102
+ toggles.cn = cn;
103
+ toggles.global = global;
104
+ togglesSnapshot = { ...toggles };
105
+ bump();
106
+ }
107
+ }
108
+ /** Read the current toggles. */
109
+ function quotaToggles() {
110
+ return togglesSnapshot;
111
+ }
112
+ /** Record a variant's sign-in state from any successful status poll. */
113
+ function noteQuotaSignIn(variantId, signedIn) {
114
+ if (variantId === "qoder" && signIn.cn !== signedIn) {
115
+ signIn.cn = signedIn;
116
+ signInSnapshot = { ...signIn };
117
+ bump();
118
+ } else if (variantId === "qoder-global" && signIn.global !== signedIn) {
119
+ signIn.global = signedIn;
120
+ signInSnapshot = { ...signIn };
121
+ bump();
122
+ }
123
+ }
124
+ /** Read the cached sign-in state. */
125
+ function quotaSignInState() {
126
+ return signInSnapshot;
127
+ }
128
+ /** Subscribe to any flag change; returns the disposer. */
129
+ function onQuotaSettingsChange(listener) {
130
+ listeners.add(listener);
131
+ return () => {
132
+ listeners.delete(listener);
133
+ };
134
+ }
135
+ /** The current revision — the useSyncExternalStore snapshot value. */
136
+ function quotaSettingsRevision() {
137
+ return revision;
138
+ }
139
+ /** Which variant a status route belongs to, from the route path. */
140
+ function variantOfStatusPath(statusPath) {
141
+ return statusPath.includes("/global/") ? "qoder-global" : "qoder";
142
+ }
123
143
  /**
124
- * The built-in header's keyboard focus ring.
144
+ * The last status document per variant, shared by EVERY quota surface.
125
145
  *
126
- * `:focus-visible` is what makes the ring appear for keyboard navigation but not
127
- * for a mouse click, and an inline style cannot express a pseudo-class — so the
128
- * component tracks it and applies this instead. Without it the header falls back
129
- * to the browser's own outline, which is the black box that used to appear on
130
- * focus where the built-in card shows a brand-coloured ring.
146
+ * The sidebar cards and the dashboard each used to fetch independently, so a
147
+ * dashboard refresh updated the panel while the sidebar card kept showing its
148
+ * previous read until its own next tick — two different numbers for one
149
+ * account on one screen. One store, one write path
150
+ * ({@link noteQuotaStatus}), and every subscriber re-renders through the
151
+ * same revision: whatever surface refreshed last, all of them show it.
152
+ *
153
+ * Documents are kept by identity (never mutated), so reference comparisons
154
+ * in useSyncExternalStore selectors stay cheap and stable.
131
155
  */
132
- const headerFocusStyle$1 = {
133
- outline: "2px solid var(--dsw-alias-brand-primary)",
134
- outlineOffset: -2
135
- };
136
- const headTextStyle$1 = {
137
- display: "flex",
138
- flex: 1,
139
- minWidth: 0,
140
- flexDirection: "column",
141
- gap: 4
142
- };
143
- const nameStyle = {
144
- fontSize: 15,
145
- lineHeight: 1.4,
146
- fontWeight: 600,
147
- color: "var(--dsw-alias-label-primary)"
156
+ const statusDocuments = {
157
+ cn: void 0,
158
+ global: void 0
148
159
  };
149
- const descriptionStyle = {
150
- fontSize: 13,
151
- lineHeight: 1.5,
152
- color: "var(--dsw-alias-label-tertiary)"
160
+ /**
161
+ * Publish one variant's freshly fetched status document. Downstream
162
+ * subscribers (both sidebar cards and the dashboard, through whichever
163
+ * observable wraps this store) re-render on the revision bump.
164
+ */
165
+ function noteQuotaStatus(variantId, status) {
166
+ if (variantId === "qoder" && statusDocuments.cn !== status) {
167
+ statusDocuments.cn = status;
168
+ statusFetchedAt.cn = Date.now();
169
+ bump();
170
+ } else if (variantId === "qoder-global" && statusDocuments.global !== status) {
171
+ statusDocuments.global = status;
172
+ statusFetchedAt.global = Date.now();
173
+ bump();
174
+ }
175
+ noteQuotaSignIn(variantId, status.status === "signed-in");
176
+ }
177
+ /** Read one variant's latest status document. */
178
+ function quotaStatus(variantId) {
179
+ return variantId === "qoder" ? statusDocuments.cn : statusDocuments.global;
180
+ }
181
+ /** When each variant's document was last fetched (per publish, not per read). */
182
+ const statusFetchedAt = {
183
+ cn: void 0,
184
+ global: void 0
153
185
  };
186
+ /** Read the time a variant's current document was fetched, if any. */
187
+ function quotaStatusFetchedAt(variantId) {
188
+ return variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
189
+ }
154
190
  /**
155
- * The disclosure chevron, drawn to match the Settings panel's own card.
191
+ * Whether ONE variant's shared document is fresh enough to skip a fetch:
192
+ * the user's rule — a click/mount/interval tick within the configured
193
+ * interval of the last successful read reuses the cached document, and only
194
+ * a variant with NO result yet (or a failed read that never landed one)
195
+ * forces the upstream call. The interval is a cache lifetime, not a metronome.
156
196
  *
157
- * The built-in card renders `IconChevronDownOutline14` from the client's shared
158
- * icon catalog, which the shell seeds into the module table. This plugin does
159
- * not request that catalog, so the same outline is drawn here from the same path
160
- * data: the text `⌄` glyph this replaces had a different shape, weight, and
161
- * baseline from the icon the cards beside it use.
197
+ * A `maxAgeMs` of the poll interval comes from the settings document; a
198
+ * failed last read is NOT tracked here (callers gate failures themselves),
199
+ * because "the last read failed" still means "no usable result".
200
+ *
201
+ * @param variantId - which variant's freshness to test.
202
+ * @param maxAgeMs - the configured poll interval (cache lifetime).
162
203
  */
163
- function ChevronDownIcon$1() {
164
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
165
- width: 14,
166
- height: 14,
167
- viewBox: "0 0 14 14",
168
- fill: "none",
169
- xmlns: "http://www.w3.org/2000/svg",
170
- "aria-hidden": "true",
171
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
172
- d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
173
- fill: "currentColor"
174
- })
175
- });
204
+ function quotaStatusIsFresh(variantId, maxAgeMs) {
205
+ const fetchedAt = variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
206
+ const document = variantId === "qoder" ? statusDocuments.cn : statusDocuments.global;
207
+ if (fetchedAt === void 0 || document === void 0) return false;
208
+ return Date.now() - fetchedAt < maxAgeMs;
176
209
  }
177
- /** The built-in card's chevron rule: tertiary color, and only the rotation animates. */
178
- const chevronStyle$1 = {
179
- flex: "none",
180
- display: "flex",
181
- color: "var(--dsw-alias-label-tertiary)",
182
- transition: "transform .16s"
183
- };
184
- const cardBodyStyle$1 = {
185
- borderTop: ".5px solid var(--dsw-alias-border-l2)",
186
- margin: "0 16px",
187
- padding: "12px 0 8px"
188
- };
189
- const bodyStyle = {
190
- margin: 0,
191
- fontSize: 13,
192
- lineHeight: 1.5,
193
- color: "var(--dsw-alias-label-tertiary)"
194
- };
195
- const rowStyle$1 = {
196
- display: "flex",
197
- alignItems: "center",
198
- justifyContent: "space-between",
199
- flexWrap: "wrap",
200
- gap: 12
201
- };
202
- const statusStyle = {
203
- display: "flex",
204
- alignItems: "center",
205
- gap: 8,
206
- fontSize: 13,
207
- fontWeight: 500,
208
- lineHeight: 1.5,
209
- color: "var(--dsw-alias-label-primary)"
210
- };
211
- /** The built-in secondary button: transparent, hairline border, 8px radius. */
212
- const buttonStyle$1 = {
213
- boxSizing: "border-box",
214
- padding: "5px 14px",
215
- border: "1px solid var(--dsw-alias-border-l2)",
216
- borderRadius: 8,
217
- background: "transparent",
218
- color: "var(--dsw-alias-label-secondary)",
219
- font: "inherit",
220
- fontSize: 13,
221
- lineHeight: 1.5,
222
- cursor: "pointer"
223
- };
224
- const errorStyle = {
225
- ...bodyStyle,
226
- color: "var(--dsw-alias-state-error-primary)"
210
+ //#endregion
211
+ //#region src/client/QuotaSettingsCard.tsx
212
+ /**
213
+ * The shared quota-settings card: one card above the two variant cards that
214
+ * configures both sidebar quota widgets.
215
+ *
216
+ * Like the built-in plugin cards, it registers into `settings.plugin.item`
217
+ * keyed by its own settings namespace (`qoder-quota`), binds that
218
+ * namespace through the client settings scope, and writes through the scope's
219
+ * revision-fenced `set` — the same durable-write path every preference row
220
+ * uses. A toggle commits on click: each click is one explicit user choice,
221
+ * and the scope's ordering makes the last one win, so no staged-draft form is
222
+ * needed for two booleans and a number.
223
+ *
224
+ * The two toggles gate the CN and international sidebar cards respectively;
225
+ * the interval is one shared poll period. Toggles are disabled while their
226
+ * variant is signed out: a quota card for an account nobody is signed into
227
+ * would render an error forever, so the setting waits for a session.
228
+ */
229
+ /** The default poll interval shown before a value is stored. */
230
+ const POLL_DEFAULT_MS = 3e5;
231
+ /** Floor the schema also enforces; mirrored here for immediate UI feedback. */
232
+ const POLL_MIN_MS = 6e4;
233
+ /** Read the section values out of a scope snapshot (defaults when absent). */
234
+ function project(scope) {
235
+ if (scope === void 0) return {
236
+ status: "unavailable",
237
+ writable: false,
238
+ values: {
239
+ sidebarQuotaCN: false,
240
+ sidebarQuotaGlobal: false,
241
+ autoCheckInCN: false,
242
+ autoCheckInGlobal: false,
243
+ quotaPollMs: POLL_DEFAULT_MS
244
+ }
245
+ };
246
+ const snapshot = scope.getSnapshot();
247
+ const value = snapshot.value ?? {};
248
+ return {
249
+ status: snapshot.status,
250
+ writable: snapshot.writable,
251
+ values: {
252
+ sidebarQuotaCN: value.sidebarQuotaCN === true,
253
+ sidebarQuotaGlobal: value.sidebarQuotaGlobal === true,
254
+ autoCheckInCN: value.autoCheckInCN === true,
255
+ autoCheckInGlobal: value.autoCheckInGlobal === true,
256
+ quotaPollMs: typeof value.quotaPollMs === "number" ? value.quotaPollMs : POLL_DEFAULT_MS
257
+ }
258
+ };
259
+ }
260
+ /**
261
+ * Stable-reference projection cache.
262
+ *
263
+ * React's useSyncExternalStore requires getSnapshot() to return THE SAME
264
+ * reference between renders unless the store actually changed. project()
265
+ * builds a fresh object every call, which re-renders forever and crashes the
266
+ * card with React error #185 ("maximum update depth exceeded") — exactly the
267
+ * crash the slot ledger reported. The cache below returns the last built
268
+ * projection until the underlying scope snapshot (or scope identity) changes,
269
+ * which is the only thing the projection actually derives from.
270
+ */
271
+ let cachedScope;
272
+ let cachedProjection;
273
+ const UNAVAILABLE = {
274
+ status: "unavailable",
275
+ writable: false,
276
+ values: {
277
+ sidebarQuotaCN: false,
278
+ sidebarQuotaGlobal: false,
279
+ autoCheckInCN: false,
280
+ autoCheckInGlobal: false,
281
+ quotaPollMs: POLL_DEFAULT_MS
282
+ }
227
283
  };
228
- const quotaListStyle = {
284
+ function stableProject(scope) {
285
+ if (scope === void 0) return UNAVAILABLE;
286
+ const next = project(scope);
287
+ if (cachedProjection === void 0 || cachedScope !== scope || cachedProjection.status !== next.status || cachedProjection.writable !== next.writable || cachedProjection.values.sidebarQuotaCN !== next.values.sidebarQuotaCN || cachedProjection.values.sidebarQuotaGlobal !== next.values.sidebarQuotaGlobal || cachedProjection.values.autoCheckInCN !== next.values.autoCheckInCN || cachedProjection.values.autoCheckInGlobal !== next.values.autoCheckInGlobal || cachedProjection.values.quotaPollMs !== next.values.quotaPollMs) {
288
+ cachedScope = scope;
289
+ cachedProjection = next;
290
+ }
291
+ return cachedProjection;
292
+ }
293
+ /** One toggle row: label, hint, and a switch drawn to the shell's proportions. */
294
+ function ToggleRow({ label, hint, checked, disabled, disabledHint, onToggle }) {
295
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
296
+ style: rowStyle$1,
297
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
298
+ style: rowTextStyle,
299
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
300
+ style: labelStyle$1,
301
+ children: label
302
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
303
+ style: hintStyle,
304
+ children: disabled === true && disabledHint !== void 0 ? disabledHint : hint
305
+ })]
306
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
307
+ type: "button",
308
+ role: "switch",
309
+ "aria-checked": checked,
310
+ disabled,
311
+ "aria-label": label,
312
+ onClick: () => {
313
+ if (disabled) return;
314
+ onToggle(!checked);
315
+ },
316
+ style: {
317
+ ...switchStyle,
318
+ background: checked ? "var(--dsw-alias-brand-primary)" : "var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.2))",
319
+ justifyContent: checked ? "flex-end" : "flex-start",
320
+ opacity: disabled === true ? .45 : 1,
321
+ cursor: disabled === true ? "not-allowed" : "pointer"
322
+ },
323
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: knobStyle })
324
+ })]
325
+ });
326
+ }
327
+ /** The inner controls for sidebar quota settings. */
328
+ function QuotaSettingsContent({ t = (key) => key, scope, signedIn }) {
329
+ const subscribe = (0, react.useCallback)((onStoreChange) => {
330
+ return scope?.subscribe(onStoreChange) ?? (() => {});
331
+ }, [scope]);
332
+ const projection = (0, react.useSyncExternalStore)(subscribe, () => stableProject(scope));
333
+ const liveSignIn = (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSignInState);
334
+ const [probe, setProbe] = (0, react.useState)();
335
+ (0, react.useEffect)(() => {
336
+ let disposed = false;
337
+ const probeOne = async (path) => {
338
+ try {
339
+ const body = await (await fetch(path, { headers: { accept: "application/json" } })).json();
340
+ if (disposed || !isQoderWebStatus(body)) return void 0;
341
+ noteQuotaSignIn(variantOfStatusPath(path), body.status === "signed-in");
342
+ return body.status === "signed-in";
343
+ } catch {
344
+ return;
345
+ }
346
+ };
347
+ (async () => {
348
+ const [cn, global] = await Promise.all([probeOne(QODER_STATUS_PATH), probeOne(QODER_GLOBAL_STATUS_PATH)]);
349
+ if (!disposed) setProbe({
350
+ cn: cn === true,
351
+ global: global === true
352
+ });
353
+ })();
354
+ return () => {
355
+ disposed = true;
356
+ };
357
+ }, []);
358
+ if (projection.status === "unavailable") return null;
359
+ const reported = signedIn?.();
360
+ const deriveSigned = (variant, variantId) => {
361
+ if (reported !== void 0) return Boolean(reported[variant]);
362
+ const currentStatus = quotaStatus(variantId);
363
+ if (currentStatus?.status === "signed-out") return false;
364
+ const live = liveSignIn[variant];
365
+ if (probe !== void 0) {
366
+ if (!probe[variant]) return Boolean(live && currentStatus?.status === "signed-in");
367
+ return Boolean(live);
368
+ }
369
+ return Boolean(live && currentStatus?.status === "signed-in");
370
+ };
371
+ const signed = {
372
+ cn: deriveSigned("cn", "qoder"),
373
+ global: deriveSigned("global", "qoder-global")
374
+ };
375
+ const write = (field, value) => {
376
+ if (field === "sidebarQuotaCN" && value === true && !signed.cn) return;
377
+ if (field === "sidebarQuotaGlobal" && value === true && !signed.global) return;
378
+ if (field === "autoCheckInCN" && value === true && !signed.cn) return;
379
+ if (field === "autoCheckInGlobal" && value === true && !signed.global) return;
380
+ scope?.set(field, value);
381
+ };
382
+ const minutes = Math.max(POLL_MIN_MS / 6e4, Math.round(projection.values.quotaPollMs / 6e4));
383
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
384
+ style: {
385
+ display: "flex",
386
+ flexDirection: "column",
387
+ gap: 4
388
+ },
389
+ children: [
390
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
391
+ label: t("quotaToggleCN"),
392
+ hint: t("quotaToggleHint"),
393
+ checked: projection.values.sidebarQuotaCN,
394
+ disabled: !signed.cn,
395
+ disabledHint: t("quotaSignInRequired"),
396
+ onToggle: (next) => write("sidebarQuotaCN", next)
397
+ }),
398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
399
+ label: t("quotaToggleGlobal"),
400
+ hint: t("quotaToggleHint"),
401
+ checked: projection.values.sidebarQuotaGlobal,
402
+ disabled: !signed.global,
403
+ disabledHint: t("quotaSignInRequired"),
404
+ onToggle: (next) => write("sidebarQuotaGlobal", next)
405
+ }),
406
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
407
+ label: t("autoCheckInCN"),
408
+ hint: t("autoCheckInHintCN"),
409
+ checked: projection.values.autoCheckInCN,
410
+ disabled: !signed.cn,
411
+ disabledHint: t("quotaSignInRequired"),
412
+ onToggle: (next) => write("autoCheckInCN", next)
413
+ }),
414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
415
+ label: t("autoCheckInGlobal"),
416
+ hint: t("autoCheckInHintGlobal"),
417
+ checked: projection.values.autoCheckInGlobal,
418
+ disabled: !signed.global,
419
+ disabledHint: t("quotaSignInRequired"),
420
+ onToggle: (next) => write("autoCheckInGlobal", next)
421
+ }),
422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
423
+ style: {
424
+ ...rowStyle$1,
425
+ borderBottom: "none",
426
+ paddingBottom: 0
427
+ },
428
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
429
+ style: rowTextStyle,
430
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
431
+ style: labelStyle$1,
432
+ children: t("quotaPollLabel")
433
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
434
+ style: hintStyle,
435
+ children: t("quotaPollHint")
436
+ })]
437
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
438
+ style: pollFieldStyle,
439
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
440
+ type: "number",
441
+ min: POLL_MIN_MS / 6e4,
442
+ step: 1,
443
+ value: minutes,
444
+ "aria-label": t("quotaPollLabel"),
445
+ onChange: (event) => {
446
+ const mins = Number.parseInt(event.target.value, 10);
447
+ if (Number.isFinite(mins) && mins > 0) write("quotaPollMs", Math.max(POLL_MIN_MS, mins * 6e4));
448
+ },
449
+ style: inputStyle
450
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
451
+ style: hintStyle,
452
+ children: t("quotaPollUnit")
453
+ })]
454
+ })]
455
+ }),
456
+ projection.writable === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
457
+ style: hintStyle,
458
+ children: t("quotaSettingsSaveFailed")
459
+ }) : null
460
+ ]
461
+ });
462
+ }
463
+ /**
464
+ * One settings row: NO box of its own (the bordered rows read as nested
465
+ * cards, which the user ruled against) — rows are separated by a hairline
466
+ * bottom rule like the settings shell's own preference lists.
467
+ */
468
+ const rowStyle$1 = {
229
469
  display: "flex",
230
- flexDirection: "column",
231
- gap: 18,
232
- paddingTop: 2
470
+ alignItems: "center",
471
+ gap: 12,
472
+ borderBottom: ".5px solid var(--dsw-alias-border-l2)",
473
+ paddingBottom: 10
233
474
  };
234
- const quotaGroupStyle = {
475
+ const rowTextStyle = {
235
476
  display: "flex",
477
+ flex: 1,
478
+ minWidth: 0,
236
479
  flexDirection: "column",
237
- gap: 10
480
+ gap: 2
238
481
  };
239
- const quotaTitleStyle = {
240
- margin: 0,
482
+ const labelStyle$1 = {
241
483
  fontSize: 13,
484
+ fontWeight: 500,
242
485
  lineHeight: 1.5,
243
- fontWeight: 600,
244
486
  color: "var(--dsw-alias-label-primary)"
245
487
  };
246
- const quotaLabelStyle = {
247
- display: "flex",
248
- justifyContent: "space-between",
249
- gap: 12,
250
- fontSize: 13,
251
- lineHeight: 1.5,
252
- color: "var(--dsw-alias-label-secondary)"
253
- };
254
- const modelRateStyle = {
488
+ const hintStyle = {
255
489
  fontSize: 12,
256
490
  lineHeight: 1.5,
257
491
  color: "var(--dsw-alias-label-tertiary)"
258
492
  };
259
- const contextPreferenceStyle = {
260
- display: "flex",
261
- alignItems: "flex-start",
262
- gap: 9,
263
- padding: "10px 12px",
264
- border: ".5px solid var(--dsw-alias-border-l4)",
265
- borderRadius: 8,
266
- background: "var(--dsw-alias-bg-layer-3)",
267
- color: "var(--dsw-alias-label-primary)",
268
- fontSize: 13,
269
- lineHeight: 1.5
270
- };
271
- const contextPreferenceCopyStyle = {
272
- display: "flex",
273
- flexDirection: "column",
274
- gap: 2
275
- };
276
- /** The right-hand cell of one model row: value and its note on one line. */
277
- const contextPickerRowStyle = {
493
+ const switchStyle = {
494
+ flex: "none",
278
495
  display: "flex",
496
+ width: 36,
497
+ height: 20,
498
+ borderRadius: 10,
499
+ borderWidth: "1px",
500
+ borderStyle: "solid",
501
+ borderColor: "var(--dsw-alias-border-l2)",
502
+ padding: 1,
503
+ cursor: "pointer",
279
504
  alignItems: "center",
280
- justifyContent: "flex-end",
281
- gap: 8,
282
- flexWrap: "wrap"
505
+ transition: "background .16s"
283
506
  };
284
- const progressTrackStyle = {
285
- height: 8,
286
- overflow: "hidden",
287
- borderRadius: 999,
288
- background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.08))"
507
+ const knobStyle = {
508
+ display: "block",
509
+ width: 16,
510
+ height: 16,
511
+ borderRadius: "50%",
512
+ background: "var(--dsw-alias-bg-layer-1, #fff)",
513
+ boxShadow: "0 1px 2px rgba(0,0,0,0.2)"
289
514
  };
290
- /**
291
- * The PAT entry row: the password field takes the row's flexible width, the
292
- * Save button keeps its own, and the whole block sits inside the card body
293
- * without a nested box (the same rule the settings rows follow).
294
- */
295
- const patRowStyle = {
515
+ const pollFieldStyle = {
516
+ display: "flex",
517
+ alignItems: "center",
518
+ gap: 6,
519
+ flex: "none"
520
+ };
521
+ const inputStyle = {
522
+ boxSizing: "border-box",
523
+ width: 55,
524
+ padding: "5px 8px",
525
+ borderWidth: "1px",
526
+ borderStyle: "solid",
527
+ borderColor: "var(--dsw-alias-border-l2)",
528
+ borderRadius: 8,
529
+ background: "var(--dsw-alias-bg-layer-2)",
530
+ color: "var(--dsw-alias-label-primary)",
531
+ font: "inherit",
532
+ fontSize: 13,
533
+ textAlign: "right"
534
+ };
535
+ //#endregion
536
+ //#region src/client/QoderPluginCard.tsx
537
+ /** Qoder status card contributed to Harness Plugin configuration. */
538
+ /** China Qoder; the plugin's primary card and default. */
539
+ const QODER_CN_CARD = {
540
+ id: "qoder",
541
+ titleKey: "title",
542
+ introKey: "intro",
543
+ signedOutKey: "signedOutHint",
544
+ patGuideKey: "patGuide",
545
+ patPlaceholderKey: "patPlaceholder",
546
+ statusPath: QODER_STATUS_PATH,
547
+ probePath: QODER_PROBE_PATH,
548
+ authPath: QODER_AUTH_PATH
549
+ };
550
+ /** International Qoder Global. */
551
+ const QODER_GLOBAL_CARD = {
552
+ id: "qoder-global",
553
+ titleKey: "titleAI",
554
+ introKey: "introAI",
555
+ signedOutKey: "signedOutHintAI",
556
+ patGuideKey: "patGuideAI",
557
+ patPlaceholderKey: "patPlaceholderAI",
558
+ statusPath: QODER_GLOBAL_STATUS_PATH,
559
+ probePath: QODER_GLOBAL_PROBE_PATH,
560
+ authPath: QODER_GLOBAL_AUTH_PATH
561
+ };
562
+ /** Both cards, in display order (China first). */
563
+ const QODER_CARD_VARIANTS = [QODER_CN_CARD, QODER_GLOBAL_CARD];
564
+ const POLL_INTERVAL_MS = 6e4;
565
+ const cardStyle = {
566
+ listStyle: "none",
567
+ borderWidth: "0.5px",
568
+ borderStyle: "solid",
569
+ borderColor: "var(--dsw-alias-border-l4)",
570
+ borderRadius: 16,
571
+ background: "var(--dsw-alias-bg-layer-3)",
572
+ transition: "border-color .16s, background .16s"
573
+ };
574
+ /** Hover, matching the built-in card's `:hover`. Inline styles cannot express a pseudo-class. */
575
+ const cardHoverStyle = { borderColor: "var(--dsw-alias-label-dimmed)" };
576
+ /** Expanded, matching the built-in card's open state. */
577
+ const cardOpenStyle = {
578
+ background: "var(--dsw-alias-bg-layer-2)",
579
+ borderColor: "var(--dsw-alias-label-dimmed)"
580
+ };
581
+ const headerStyle = {
582
+ boxSizing: "border-box",
583
+ width: "100%",
584
+ display: "flex",
585
+ alignItems: "center",
586
+ gap: 12,
587
+ border: 0,
588
+ borderRadius: 12,
589
+ padding: "14px 16px",
590
+ background: "transparent",
591
+ color: "inherit",
592
+ font: "inherit",
593
+ textAlign: "left",
594
+ cursor: "pointer",
595
+ appearance: "none"
596
+ };
597
+ /**
598
+ * The built-in header's keyboard focus ring.
599
+ *
600
+ * `:focus-visible` is what makes the ring appear for keyboard navigation but not
601
+ * for a mouse click, and an inline style cannot express a pseudo-class — so the
602
+ * component tracks it and applies this instead. Without it the header falls back
603
+ * to the browser's own outline, which is the black box that used to appear on
604
+ * focus where the built-in card shows a brand-coloured ring.
605
+ */
606
+ const headerFocusStyle = {
607
+ outline: "2px solid var(--dsw-alias-brand-primary)",
608
+ outlineOffset: -2
609
+ };
610
+ const headTextStyle = {
611
+ display: "flex",
612
+ flex: 1,
613
+ minWidth: 0,
614
+ flexDirection: "column",
615
+ gap: 4
616
+ };
617
+ const nameStyle = {
618
+ fontSize: 15,
619
+ lineHeight: 1.4,
620
+ fontWeight: 600,
621
+ color: "var(--dsw-alias-label-primary)"
622
+ };
623
+ const descriptionStyle = {
624
+ fontSize: 13,
625
+ lineHeight: 1.5,
626
+ color: "var(--dsw-alias-label-tertiary)"
627
+ };
628
+ /**
629
+ * The disclosure chevron, drawn to match the Settings panel's own card.
630
+ *
631
+ * The built-in card renders `IconChevronDownOutline14` from the client's shared
632
+ * icon catalog, which the shell seeds into the module table. This plugin does
633
+ * not request that catalog, so the same outline is drawn here from the same path
634
+ * data: the text `⌄` glyph this replaces had a different shape, weight, and
635
+ * baseline from the icon the cards beside it use.
636
+ */
637
+ function ChevronDownIcon() {
638
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
639
+ width: 14,
640
+ height: 14,
641
+ viewBox: "0 0 14 14",
642
+ fill: "none",
643
+ xmlns: "http://www.w3.org/2000/svg",
644
+ "aria-hidden": "true",
645
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
646
+ d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
647
+ fill: "currentColor"
648
+ })
649
+ });
650
+ }
651
+ /** The built-in card's chevron rule: tertiary color, and only the rotation animates. */
652
+ const chevronStyle = {
653
+ flex: "none",
654
+ display: "flex",
655
+ color: "var(--dsw-alias-label-tertiary)",
656
+ transition: "transform .16s"
657
+ };
658
+ const cardBodyStyle = {
659
+ borderTop: ".5px solid var(--dsw-alias-border-l2)",
660
+ margin: "0 16px",
661
+ padding: "12px 0 8px"
662
+ };
663
+ const bodyStyle = {
664
+ margin: 0,
665
+ fontSize: 13,
666
+ lineHeight: 1.5,
667
+ color: "var(--dsw-alias-label-tertiary)"
668
+ };
669
+ const rowStyle = {
670
+ display: "flex",
671
+ alignItems: "center",
672
+ justifyContent: "space-between",
673
+ flexWrap: "wrap",
674
+ gap: 12
675
+ };
676
+ const statusStyle = {
677
+ display: "flex",
678
+ alignItems: "center",
679
+ gap: 8,
680
+ fontSize: 13,
681
+ fontWeight: 500,
682
+ lineHeight: 1.5,
683
+ color: "var(--dsw-alias-label-primary)"
684
+ };
685
+ /** The built-in secondary button: transparent, hairline border, 8px radius. */
686
+ const buttonStyle$1 = {
687
+ boxSizing: "border-box",
688
+ padding: "5px 14px",
689
+ border: "1px solid var(--dsw-alias-border-l2)",
690
+ borderRadius: 8,
691
+ background: "transparent",
692
+ color: "var(--dsw-alias-label-secondary)",
693
+ font: "inherit",
694
+ fontSize: 13,
695
+ lineHeight: 1.5,
696
+ cursor: "pointer"
697
+ };
698
+ const errorStyle = {
699
+ ...bodyStyle,
700
+ color: "var(--dsw-alias-state-error-primary)"
701
+ };
702
+ const quotaListStyle = {
703
+ display: "flex",
704
+ flexDirection: "column",
705
+ gap: 18,
706
+ paddingTop: 2
707
+ };
708
+ const quotaGroupStyle = {
709
+ display: "flex",
710
+ flexDirection: "column",
711
+ gap: 10
712
+ };
713
+ const quotaTitleStyle = {
714
+ margin: 0,
715
+ fontSize: 13,
716
+ lineHeight: 1.5,
717
+ fontWeight: 600,
718
+ color: "var(--dsw-alias-label-primary)"
719
+ };
720
+ const quotaLabelStyle = {
721
+ display: "flex",
722
+ justifyContent: "space-between",
723
+ gap: 12,
724
+ fontSize: 13,
725
+ lineHeight: 1.5,
726
+ color: "var(--dsw-alias-label-secondary)"
727
+ };
728
+ const modelRateStyle = {
729
+ fontSize: 12,
730
+ lineHeight: 1.5,
731
+ color: "var(--dsw-alias-label-tertiary)"
732
+ };
733
+ const contextPreferenceStyle = {
734
+ display: "flex",
735
+ alignItems: "flex-start",
736
+ gap: 9,
737
+ padding: "10px 12px",
738
+ border: ".5px solid var(--dsw-alias-border-l4)",
739
+ borderRadius: 8,
740
+ background: "var(--dsw-alias-bg-layer-3)",
741
+ color: "var(--dsw-alias-label-primary)",
742
+ fontSize: 13,
743
+ lineHeight: 1.5
744
+ };
745
+ const contextPreferenceCopyStyle = {
746
+ display: "flex",
747
+ flexDirection: "column",
748
+ gap: 2
749
+ };
750
+ const contextPickerRowStyle = {
751
+ display: "flex",
752
+ alignItems: "center",
753
+ justifyContent: "flex-end",
754
+ gap: 8,
755
+ flexWrap: "wrap"
756
+ };
757
+ const progressTrackStyle = {
758
+ height: 8,
759
+ overflow: "hidden",
760
+ borderRadius: 999,
761
+ background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.08))"
762
+ };
763
+ /**
764
+ * The PAT entry row: the password field takes the row's flexible width, the
765
+ * Save button keeps its own, and the whole block sits inside the card body
766
+ * without a nested box (the same rule the settings rows follow).
767
+ */
768
+ const patRowStyle = {
296
769
  display: "flex",
297
770
  alignItems: "center",
298
771
  gap: 8,
@@ -353,13 +826,51 @@ window.__ModuleLoader__.load({
353
826
  */
354
827
  const primaryButtonStyle$1 = {
355
828
  ...buttonStyle$1,
356
- border: "1px solid var(--dsw-alias-button-primary-fill)",
829
+ borderWidth: "1px",
830
+ borderStyle: "solid",
831
+ borderColor: "var(--dsw-alias-button-primary-fill)",
357
832
  background: "var(--dsw-alias-button-primary-fill)",
358
833
  color: "var(--dsw-alias-label-primary-foreground)"
359
834
  };
360
- function progressFillStyle(percent) {
361
- return {
362
- width: `${Math.max(0, Math.min(100, percent))}%`,
835
+ const segmentedContainerStyle = {
836
+ display: "flex",
837
+ alignItems: "center",
838
+ background: "var(--dsw-alias-bg-layer-1, rgba(20, 20, 20, 0.6))",
839
+ borderWidth: "1px",
840
+ borderStyle: "solid",
841
+ borderColor: "var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.08))",
842
+ borderRadius: 8,
843
+ padding: 3,
844
+ gap: 4,
845
+ marginTop: 14,
846
+ marginBottom: 16
847
+ };
848
+ function segmentedTabItemStyle(active) {
849
+ return {
850
+ flex: 1,
851
+ display: "flex",
852
+ alignItems: "center",
853
+ justifyContent: "center",
854
+ gap: 8,
855
+ padding: "6px 12px",
856
+ borderRadius: 6,
857
+ borderWidth: "1px",
858
+ borderStyle: "solid",
859
+ borderColor: active ? "var(--dsw-alias-border-l4, rgba(255, 255, 255, 0.18))" : "transparent",
860
+ background: active ? "var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.08))" : "transparent",
861
+ color: active ? "var(--dsw-alias-label-primary, #fff)" : "var(--dsw-alias-label-tertiary, #8c8c8c)",
862
+ fontWeight: active ? 500 : 400,
863
+ fontSize: 13,
864
+ lineHeight: "18px",
865
+ cursor: "pointer",
866
+ appearance: "none",
867
+ outline: "none",
868
+ transition: "all .16s ease"
869
+ };
870
+ }
871
+ function progressFillStyle(percent) {
872
+ return {
873
+ width: `${Math.max(0, Math.min(100, percent))}%`,
363
874
  height: "100%",
364
875
  borderRadius: "inherit",
365
876
  background: "var(--dsw-alias-brand-primary, #1677ff)"
@@ -373,11 +884,11 @@ window.__ModuleLoader__.load({
373
884
  */
374
885
  function dotStyle(status) {
375
886
  return {
376
- width: 9,
377
- height: 9,
887
+ width: 8,
888
+ height: 8,
378
889
  borderRadius: "50%",
379
890
  flex: "0 0 auto",
380
- background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)"
891
+ background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #8c8c8c)"
381
892
  };
382
893
  }
383
894
  function formatNumber(value) {
@@ -397,7 +908,6 @@ window.__ModuleLoader__.load({
397
908
  if (!Number.isNaN(parsed)) return formatTime(parsed);
398
909
  return time;
399
910
  }
400
- /** Localize where the credential in effect came from. */
401
911
  function patSourceText(source, t) {
402
912
  if (source === "env") return t("patSourceEnv");
403
913
  if (source === "cli") return t("patSourceCli");
@@ -549,72 +1059,168 @@ window.__ModuleLoader__.load({
549
1059
  ]
550
1060
  });
551
1061
  }
552
- /**
553
- * Compact token count for display: the catalog's own round numbers (`200000`,
554
- * `1000000`) read better as `200K` / `1M`, and no precision is lost because
555
- * these values are always whole thousands.
556
- */
557
1062
  function formatTokens(tokens) {
558
1063
  if (tokens >= 1e6 && tokens % 1e6 === 0) return `${tokens / 1e6}M`;
559
1064
  if (tokens >= 1e3 && tokens % 1e3 === 0) return `${tokens / 1e3}K`;
560
1065
  return String(tokens);
561
1066
  }
1067
+ function CheckInLogTable({ logs = [], t, onCheckIn, onRefresh, onClear, busy, checkingIn, clearing, disabled, notice }) {
1068
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1069
+ style: quotaListStyle,
1070
+ children: [
1071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1072
+ style: {
1073
+ display: "flex",
1074
+ alignItems: "center",
1075
+ justifyContent: "space-between",
1076
+ flexWrap: "wrap",
1077
+ gap: 8
1078
+ },
1079
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1080
+ style: quotaTitleStyle,
1081
+ children: t("tabCheckIn")
1082
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1083
+ style: {
1084
+ display: "flex",
1085
+ alignItems: "center",
1086
+ gap: 6
1087
+ },
1088
+ children: [
1089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1090
+ type: "button",
1091
+ style: buttonStyle$1,
1092
+ disabled: disabled || busy || checkingIn,
1093
+ onClick: onCheckIn,
1094
+ children: checkingIn ? t("checkInChecking") : t("checkInNow")
1095
+ }),
1096
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1097
+ type: "button",
1098
+ style: buttonStyle$1,
1099
+ disabled: busy || checkingIn,
1100
+ onClick: onRefresh,
1101
+ children: busy ? t("checkInRefreshing") : t("checkInRefresh")
1102
+ }),
1103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1104
+ type: "button",
1105
+ style: buttonStyle$1,
1106
+ disabled: busy || clearing || !logs || logs.length === 0,
1107
+ onClick: onClear,
1108
+ children: clearing ? t("checkInClearing") : t("checkInClear")
1109
+ })
1110
+ ]
1111
+ })]
1112
+ }),
1113
+ notice === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1114
+ style: bodyStyle,
1115
+ children: notice
1116
+ }),
1117
+ !logs || logs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1118
+ style: descriptionStyle,
1119
+ children: t("checkInLogEmpty")
1120
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1121
+ style: {
1122
+ display: "flex",
1123
+ flexDirection: "column",
1124
+ gap: 6
1125
+ },
1126
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1127
+ style: {
1128
+ display: "flex",
1129
+ borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.15))",
1130
+ paddingBottom: 6,
1131
+ fontSize: 12,
1132
+ color: "var(--dsw-alias-label-tertiary)"
1133
+ },
1134
+ children: [
1135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1136
+ style: { flex: 2 },
1137
+ children: t("checkInLogTime")
1138
+ }),
1139
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1140
+ style: { flex: 3 },
1141
+ children: t("checkInLogResult")
1142
+ }),
1143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1144
+ style: {
1145
+ flex: 1,
1146
+ textAlign: "right"
1147
+ },
1148
+ children: t("checkInLogAmount")
1149
+ })
1150
+ ]
1151
+ }), logs.map((log) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1152
+ style: {
1153
+ display: "flex",
1154
+ alignItems: "center",
1155
+ padding: "6px 0",
1156
+ fontSize: 13,
1157
+ borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.08))"
1158
+ },
1159
+ children: [
1160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1161
+ style: {
1162
+ flex: 2,
1163
+ color: "var(--dsw-alias-label-secondary)"
1164
+ },
1165
+ children: formatTime(log.timestamp)
1166
+ }),
1167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1168
+ style: {
1169
+ flex: 3,
1170
+ display: "flex",
1171
+ alignItems: "center",
1172
+ gap: 6
1173
+ },
1174
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
1175
+ width: 6,
1176
+ height: 6,
1177
+ borderRadius: "50%",
1178
+ flexShrink: 0,
1179
+ background: log.status === "claimed" ? "var(--dsw-alias-status-success, #52c41a)" : log.status === "already-claimed" ? "var(--dsw-alias-status-info, #1890ff)" : log.status === "no-campaign" ? "var(--dsw-alias-label-tertiary, #999)" : "var(--dsw-alias-status-error, #f5222d)"
1180
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: log.status === "claimed" ? t("autoCheckInStatusClaimed", { amount: log.amount ?? 100 }) : log.status === "already-claimed" ? t("autoCheckInStatusAlready") : log.status === "no-campaign" ? t("autoCheckInStatusNoCampaign") : t("autoCheckInStatusError", { message: log.message ?? "" }) })]
1181
+ }),
1182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1183
+ style: {
1184
+ flex: 1,
1185
+ textAlign: "right",
1186
+ fontWeight: 600,
1187
+ color: log.amount ? "var(--dsw-alias-brand-primary)" : "inherit"
1188
+ },
1189
+ children: log.amount ? `+${log.amount}` : "-"
1190
+ })
1191
+ ]
1192
+ }, log.id))]
1193
+ })
1194
+ ]
1195
+ });
1196
+ }
562
1197
  /** Render Qoder PAT state, quota, catalog, and context capacities as one expandable card. */
563
- function QoderPluginCard({ t, variant = QODER_CN_CARD }) {
1198
+ function QoderPluginCard(props) {
1199
+ const { t, scope, signedIn, variant, unified } = props;
564
1200
  if (t === void 0) throw new Error("Qoder plugin card requires its translation function");
1201
+ const isUnified = unified === true;
1202
+ const liveSignIn = (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSignInState);
1203
+ const [activeVariantId, setActiveVariantId] = (0, react.useState)("qoder");
1204
+ const currentVariant = isUnified ? activeVariantId === "qoder" ? QODER_CN_CARD : QODER_GLOBAL_CARD : variant ?? QODER_CN_CARD;
565
1205
  const [open, setOpen] = (0, react.useState)(false);
566
- /** Whether the pointer is over the card; drives the same border tint the built-in card gets on hover. */
567
1206
  const [hovered, setHovered] = (0, react.useState)(false);
568
- /** Keyboard focus on the header, reproducing the built-in's `:focus-visible` ring. */
569
1207
  const [headerFocused, setHeaderFocused] = (0, react.useState)(false);
570
- /**
571
- * The document to render. `undefined` means *not read yet*, which is a
572
- * distinct state from "signed out": seeding this with a signed-out document
573
- * told an already-signed-in user they were signed out for the whole first
574
- * round trip (and forever, if the read never settled).
575
- */
576
1208
  const [status, setStatus] = (0, react.useState)();
577
- /**
578
- * Whether the last **successful** read found a usable credential.
579
- *
580
- * Kept apart from `status` because the poll's liveness must depend on what the
581
- * account actually is, not on what the card last displayed: a failed read
582
- * leaves this untouched, so a transient failure cannot disarm the interval,
583
- * while a genuine signed-out answer still stops it.
584
- *
585
- * `undefined` therefore means "no successful read yet", which is also the
586
- * condition that decides whether a failed read has anything to preserve.
587
- */
588
- const [signedIn, setSignedIn] = (0, react.useState)();
589
- /**
590
- * Why the most recent read failed, when it did. Rendered as a notice beside
591
- * whatever document is still on screen, rather than replacing it.
592
- */
1209
+ const [signedInState, setSignedInState] = (0, react.useState)();
593
1210
  const [readFailure, setReadFailure] = (0, react.useState)();
594
1211
  const [busy, setBusy] = (0, react.useState)(false);
595
- /** The PAT draft in the input box. Never persisted; lives only in component state. */
596
1212
  const [patDraft, setPatDraft] = (0, react.useState)("");
597
- /** Whether the signed-in card is showing the replace-PAT entry. */
598
1213
  const [replacing, setReplacing] = (0, react.useState)(false);
599
- /** Whether a PAT save or clear is in flight. Own flag, so the 60s busy label stays honest. */
600
1214
  const [patBusy, setPatBusy] = (0, react.useState)(false);
601
- /** Why the most recent PAT save/clear failed, when it did. */
602
1215
  const [patError, setPatError] = (0, react.useState)();
603
- /** The most recent PAT outcome the card should report beside the entry. */
604
1216
  const [patNotice, setPatNotice] = (0, react.useState)();
605
- /** The password input, so 「更换 PAT」 can focus and pre-clear it. */
606
1217
  const patInput = (0, react.useRef)(null);
607
1218
  const [tab, setTab] = (0, react.useState)("status");
1219
+ const [checkingIn, setCheckingIn] = (0, react.useState)(false);
1220
+ const [clearingLogs, setClearingLogs] = (0, react.useState)(false);
1221
+ const [checkInNotice, setCheckInNotice] = (0, react.useState)();
608
1222
  const mounted = (0, react.useRef)(true);
609
- /**
610
- * Identity of the newest read that may write. Assigned when a read *starts*,
611
- * so a response is superseded by anything begun after it — "the response whose
612
- * request started last wins". Without this, a slow poll begun before a manual
613
- * action could settle after the action's own refresh and restore the older
614
- * document.
615
- */
616
1223
  const readSeq = (0, react.useRef)(0);
617
- /** Manual requests in flight, so unmount can abort them like the poll's. */
618
1224
  const manualControllers = (0, react.useRef)(/* @__PURE__ */ new Set());
619
1225
  (0, react.useEffect)(() => {
620
1226
  mounted.current = true;
@@ -624,39 +1230,17 @@ window.__ModuleLoader__.load({
624
1230
  manualControllers.current.clear();
625
1231
  };
626
1232
  }, []);
627
- /** Register a manual request's controller so unmount aborts it. */
628
1233
  const trackController = (0, react.useCallback)(() => {
629
1234
  const controller = new AbortController();
630
1235
  manualControllers.current.add(controller);
631
1236
  return controller;
632
1237
  }, []);
633
- /**
634
- * The in-process key authorizing this card's PAT writes, or undefined until a
635
- * document carrying one has been read.
636
- *
637
- * Derived once rather than read off each use site: the `error` arm carries no
638
- * key, and reaching for `status.authKey` in three places is three chances to
639
- * dereference a state that has none. The constant-time comparison happens on
640
- * the host; the card only passes the key through.
641
- */
642
1238
  const authKey = status === void 0 || status.status === "error" ? void 0 : status.authKey;
643
- /**
644
- * Read the status document and apply it under the two policies the card's
645
- * correctness rests on:
646
- *
647
- * - a non-document body (empty, `null`, a non-JSON page) is a failed read, not
648
- * something to store and then dereference in the render;
649
- * - a failed read never discards a document already on screen. It is recorded
650
- * and shown as a notice beside that document; only when nothing has been
651
- * read yet does the failure itself become the rendered state.
652
- *
653
- * Returns whether this read produced the current document.
654
- */
655
1239
  const refresh = (0, react.useCallback)(async (signal) => {
656
1240
  const seq = ++readSeq.current;
657
1241
  const current = () => mounted.current && signal?.aborted !== true && seq === readSeq.current;
658
1242
  try {
659
- const response = await fetch(variant.statusPath, {
1243
+ const response = await fetch(currentVariant.statusPath, {
660
1244
  headers: { accept: "application/json" },
661
1245
  credentials: "same-origin",
662
1246
  ...signal === void 0 ? {} : { signal }
@@ -666,8 +1250,13 @@ window.__ModuleLoader__.load({
666
1250
  if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
667
1251
  if (!current()) return false;
668
1252
  setStatus(value);
669
- if (value.status === "signed-in") setSignedIn(true);
670
- else if (value.status === "signed-out") setSignedIn(false);
1253
+ if (value.status === "signed-in") {
1254
+ setSignedInState(true);
1255
+ noteQuotaStatus(currentVariant.id, value);
1256
+ } else if (value.status === "signed-out") {
1257
+ setSignedInState(false);
1258
+ noteQuotaStatus(currentVariant.id, value);
1259
+ }
671
1260
  setReadFailure(void 0);
672
1261
  return true;
673
1262
  } catch (error) {
@@ -681,17 +1270,28 @@ window.__ModuleLoader__.load({
681
1270
  }
682
1271
  return false;
683
1272
  }
684
- }, [t, variant.statusPath]);
1273
+ }, [currentVariant.statusPath, t]);
685
1274
  (0, react.useEffect)(() => {
686
1275
  if (!open) return;
1276
+ setStatus(void 0);
1277
+ setSignedInState(void 0);
1278
+ setReadFailure(void 0);
1279
+ setPatDraft("");
1280
+ setReplacing(false);
1281
+ setPatError(void 0);
1282
+ setPatNotice(void 0);
687
1283
  const controller = new AbortController();
688
1284
  refresh(controller.signal);
689
1285
  return () => {
690
1286
  controller.abort();
691
1287
  };
692
- }, [open, refresh]);
1288
+ }, [
1289
+ open,
1290
+ currentVariant.statusPath,
1291
+ refresh
1292
+ ]);
693
1293
  (0, react.useEffect)(() => {
694
- if (!open || signedIn === false) return;
1294
+ if (!open || signedInState === false) return;
695
1295
  const controller = new AbortController();
696
1296
  const timer = window.setInterval(() => {
697
1297
  refresh(controller.signal);
@@ -703,7 +1303,7 @@ window.__ModuleLoader__.load({
703
1303
  }, [
704
1304
  open,
705
1305
  refresh,
706
- signedIn
1306
+ signedInState
707
1307
  ]);
708
1308
  const manualRefresh = async () => {
709
1309
  setBusy(true);
@@ -729,7 +1329,7 @@ window.__ModuleLoader__.load({
729
1329
  setBusy(true);
730
1330
  const controller = trackController();
731
1331
  try {
732
- const response = await fetch(variant.probePath, {
1332
+ const response = await fetch(currentVariant.probePath, {
733
1333
  method: "POST",
734
1334
  headers: {
735
1335
  "Content-Type": "application/json",
@@ -753,11 +1353,88 @@ window.__ModuleLoader__.load({
753
1353
  manualControllers.current.delete(controller);
754
1354
  }
755
1355
  }, [
1356
+ currentVariant.probePath,
1357
+ refresh,
1358
+ status,
1359
+ t,
1360
+ trackController
1361
+ ]);
1362
+ const manualCheckIn = (0, react.useCallback)(async () => {
1363
+ const key = status?.status === "signed-in" ? status.probeKey : void 0;
1364
+ if (key === void 0) return;
1365
+ setCheckingIn(true);
1366
+ setCheckInNotice(void 0);
1367
+ const controller = trackController();
1368
+ try {
1369
+ const response = await fetch(currentVariant.probePath, {
1370
+ method: "POST",
1371
+ headers: {
1372
+ "Content-Type": "application/json",
1373
+ "X-Qoder-Probe-Key": key
1374
+ },
1375
+ credentials: "same-origin",
1376
+ signal: controller.signal,
1377
+ body: JSON.stringify({ action: "checkin" })
1378
+ });
1379
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1380
+ const result = await response.json();
1381
+ if (result.state === "claimed") setCheckInNotice(t("autoCheckInStatusClaimed", { amount: result.amount ?? 100 }));
1382
+ else if (result.state === "already-claimed") setCheckInNotice(t("autoCheckInStatusAlready"));
1383
+ else if (result.state === "no-campaign") setCheckInNotice(t("autoCheckInStatusNoCampaign"));
1384
+ else if (result.reason) setCheckInNotice(t("autoCheckInStatusError", { message: result.reason }));
1385
+ } catch (error) {
1386
+ if (mounted.current && controller.signal.aborted !== true) setCheckInNotice(error instanceof Error ? error.message : t("requestFailed"));
1387
+ } finally {
1388
+ manualControllers.current.delete(controller);
1389
+ if (mounted.current) setCheckingIn(false);
1390
+ }
1391
+ try {
1392
+ await refresh(controller.signal);
1393
+ } finally {
1394
+ manualControllers.current.delete(controller);
1395
+ }
1396
+ }, [
1397
+ currentVariant.probePath,
1398
+ refresh,
1399
+ status,
1400
+ t,
1401
+ trackController
1402
+ ]);
1403
+ const clearCheckInLogs = (0, react.useCallback)(async () => {
1404
+ const key = status?.status === "signed-in" ? status.probeKey : void 0;
1405
+ if (key === void 0) return;
1406
+ setClearingLogs(true);
1407
+ setCheckInNotice(void 0);
1408
+ const controller = trackController();
1409
+ try {
1410
+ const response = await fetch(currentVariant.probePath, {
1411
+ method: "POST",
1412
+ headers: {
1413
+ "Content-Type": "application/json",
1414
+ "X-Qoder-Probe-Key": key
1415
+ },
1416
+ credentials: "same-origin",
1417
+ signal: controller.signal,
1418
+ body: JSON.stringify({ action: "clear-checkin-logs" })
1419
+ });
1420
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1421
+ } catch (error) {
1422
+ if (mounted.current && controller.signal.aborted !== true) setCheckInNotice(error instanceof Error ? error.message : t("requestFailed"));
1423
+ } finally {
1424
+ manualControllers.current.delete(controller);
1425
+ if (mounted.current) setClearingLogs(false);
1426
+ }
1427
+ try {
1428
+ await refresh(controller.signal);
1429
+ } finally {
1430
+ manualControllers.current.delete(controller);
1431
+ }
1432
+ }, [
1433
+ currentVariant.probePath,
756
1434
  refresh,
757
1435
  status,
758
1436
  t,
759
- trackController,
760
- variant.probePath
1437
+ trackController
761
1438
  ]);
762
1439
  /**
763
1440
  * Run one probe-route control action and refresh the card's state afterwards.
@@ -772,7 +1449,7 @@ window.__ModuleLoader__.load({
772
1449
  setBusy(true);
773
1450
  const controller = trackController();
774
1451
  try {
775
- const response = await fetch(variant.probePath, {
1452
+ const response = await fetch(currentVariant.probePath, {
776
1453
  method: "POST",
777
1454
  headers: {
778
1455
  "Content-Type": "application/json",
@@ -799,11 +1476,11 @@ window.__ModuleLoader__.load({
799
1476
  if (mounted.current) setBusy(false);
800
1477
  }
801
1478
  }, [
1479
+ currentVariant.probePath,
802
1480
  refresh,
803
1481
  status,
804
1482
  t,
805
- trackController,
806
- variant.probePath
1483
+ trackController
807
1484
  ]);
808
1485
  /**
809
1486
  * Validate and store the pasted PAT (or overwrite the stored one).
@@ -823,7 +1500,7 @@ window.__ModuleLoader__.load({
823
1500
  setPatNotice(void 0);
824
1501
  const controller = trackController();
825
1502
  try {
826
- const response = await fetch(variant.authPath, {
1503
+ const response = await fetch(currentVariant.authPath, {
827
1504
  method: "POST",
828
1505
  headers: {
829
1506
  "Content-Type": "application/json",
@@ -850,6 +1527,7 @@ window.__ModuleLoader__.load({
850
1527
  setPatDraft("");
851
1528
  setReplacing(false);
852
1529
  setPatNotice(t("patSaved"));
1530
+ noteQuotaSignIn(currentVariant.id, true);
853
1531
  await refresh(controller.signal);
854
1532
  } catch (error) {
855
1533
  if (mounted.current && controller.signal.aborted !== true) setPatError(t("patSaveFailed", { message: error instanceof Error ? error.message : t("requestFailed") }));
@@ -859,12 +1537,12 @@ window.__ModuleLoader__.load({
859
1537
  }
860
1538
  }, [
861
1539
  authKey,
1540
+ currentVariant.authPath,
862
1541
  patBusy,
863
1542
  patDraft,
864
1543
  refresh,
865
1544
  t,
866
- trackController,
867
- variant.authPath
1545
+ trackController
868
1546
  ]);
869
1547
  /** Remove the stored PAT; the host answer is `{ ok: true }`, then the card re-reads. */
870
1548
  const clearPat = (0, react.useCallback)(async () => {
@@ -876,7 +1554,7 @@ window.__ModuleLoader__.load({
876
1554
  setReplacing(false);
877
1555
  const controller = trackController();
878
1556
  try {
879
- const response = await fetch(variant.authPath, {
1557
+ const response = await fetch(currentVariant.authPath, {
880
1558
  method: "POST",
881
1559
  headers: {
882
1560
  "Content-Type": "application/json",
@@ -894,6 +1572,14 @@ window.__ModuleLoader__.load({
894
1572
  return;
895
1573
  }
896
1574
  setPatDraft("");
1575
+ const signedOutDoc = {
1576
+ status: "signed-out",
1577
+ authKey: key
1578
+ };
1579
+ setStatus(signedOutDoc);
1580
+ setSignedInState(false);
1581
+ noteQuotaStatus(currentVariant.id, signedOutDoc);
1582
+ noteQuotaSignIn(currentVariant.id, false);
897
1583
  await refresh(controller.signal);
898
1584
  } catch (error) {
899
1585
  if (mounted.current && controller.signal.aborted !== true) setPatError(t("patSaveFailed", { message: error instanceof Error ? error.message : t("requestFailed") }));
@@ -903,11 +1589,11 @@ window.__ModuleLoader__.load({
903
1589
  }
904
1590
  }, [
905
1591
  authKey,
1592
+ currentVariant.authPath,
906
1593
  patBusy,
907
1594
  refresh,
908
1595
  t,
909
- trackController,
910
- variant.authPath
1596
+ trackController
911
1597
  ]);
912
1598
  /**
913
1599
  * 更换 PAT: open the entry over the signed-in document, pre-cleared and
@@ -925,7 +1611,6 @@ window.__ModuleLoader__.load({
925
1611
  patInput.current?.focus();
926
1612
  });
927
1613
  }, []);
928
- /** The PAT entry: guide line, password input, Save. Shared by both arms. */
929
1614
  const patEntry = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
930
1615
  style: {
931
1616
  display: "flex",
@@ -935,7 +1620,7 @@ window.__ModuleLoader__.load({
935
1620
  children: [
936
1621
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
937
1622
  style: bodyStyle,
938
- children: t(variant.patGuideKey)
1623
+ children: t(currentVariant.patGuideKey)
939
1624
  }),
940
1625
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
941
1626
  style: patRowStyle,
@@ -944,7 +1629,7 @@ window.__ModuleLoader__.load({
944
1629
  ref: patInput,
945
1630
  type: "password",
946
1631
  value: patDraft,
947
- placeholder: t(variant.patPlaceholderKey),
1632
+ placeholder: t(currentVariant.patPlaceholderKey),
948
1633
  "aria-label": t("patHeading"),
949
1634
  disabled: patBusy,
950
1635
  onChange: (event) => {
@@ -990,9 +1675,9 @@ window.__ModuleLoader__.load({
990
1675
  })
991
1676
  ]
992
1677
  });
993
- const title = t(variant.titleKey);
1678
+ const cardTitle = isUnified ? t("unifiedTitle") : t(currentVariant.titleKey);
1679
+ const cardIntro = isUnified ? t("unifiedIntro") : t(currentVariant.introKey);
994
1680
  const label = status === void 0 ? t("loading") : status.status === "signed-in" ? t("signedIn") : status.status === "error" ? t("requestFailed") : t("signedOut");
995
- /** One line summarizing the PAT in effect: source, saved-at, and the redacted tail. */
996
1681
  const patSummaryLine = (pat) => {
997
1682
  const parts = [
998
1683
  patSourceText(pat.source, t),
@@ -1004,11 +1689,16 @@ window.__ModuleLoader__.load({
1004
1689
  children: parts.join(" · ")
1005
1690
  });
1006
1691
  };
1692
+ const reported = signedIn?.();
1693
+ const cnSignedIn = reported !== void 0 ? reported.cn : liveSignIn.cn;
1694
+ const globalSignedIn = reported !== void 0 ? reported.global : liveSignIn.global;
1695
+ const cnDotStatus = isUnified && activeVariantId === "qoder" ? status === void 0 ? "loading" : status.status : cnSignedIn ? "signed-in" : "signed-out";
1696
+ const globalDotStatus = isUnified && activeVariantId === "qoder-global" ? status === void 0 ? "loading" : status.status : globalSignedIn ? "signed-in" : "signed-out";
1007
1697
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1008
1698
  style: {
1009
- ...cardStyle$1,
1010
- ...hovered ? cardHoverStyle$1 : {},
1011
- ...open ? cardOpenStyle$1 : {}
1699
+ ...cardStyle,
1700
+ ...hovered ? cardHoverStyle : {},
1701
+ ...open ? cardOpenStyle : {}
1012
1702
  },
1013
1703
  onMouseEnter: () => {
1014
1704
  setHovered(true);
@@ -1020,10 +1710,10 @@ window.__ModuleLoader__.load({
1020
1710
  type: "button",
1021
1711
  style: {
1022
1712
  ...headerStyle,
1023
- ...headerFocused ? headerFocusStyle$1 : {}
1713
+ ...headerFocused ? headerFocusStyle : {}
1024
1714
  },
1025
1715
  "aria-expanded": open,
1026
- "aria-label": `${t(open ? "collapse" : "expand")}: ${title}`,
1716
+ "aria-label": `${t(open ? "collapse" : "expand")}: ${cardTitle}`,
1027
1717
  onClick: () => {
1028
1718
  setOpen(!open);
1029
1719
  },
@@ -1040,30 +1730,60 @@ window.__ModuleLoader__.load({
1040
1730
  setHeaderFocused(false);
1041
1731
  },
1042
1732
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1043
- style: headTextStyle$1,
1733
+ style: headTextStyle,
1044
1734
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1045
1735
  style: nameStyle,
1046
- children: title
1736
+ children: cardTitle
1047
1737
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1048
1738
  style: descriptionStyle,
1049
- children: t(variant.introKey)
1739
+ children: cardIntro
1050
1740
  })]
1051
1741
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1052
1742
  style: {
1053
- ...chevronStyle$1,
1743
+ ...chevronStyle,
1054
1744
  transform: open ? "rotate(180deg)" : "none"
1055
1745
  },
1056
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon$1, {})
1746
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, {})
1057
1747
  })]
1058
1748
  }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1059
- style: cardBodyStyle$1,
1749
+ style: cardBodyStyle,
1060
1750
  children: [
1751
+ isUnified ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaSettingsContent, {
1752
+ t,
1753
+ scope,
1754
+ signedIn
1755
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1756
+ style: segmentedContainerStyle,
1757
+ role: "tablist",
1758
+ "aria-label": "Qoder Version Selection",
1759
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1760
+ type: "button",
1761
+ role: "tab",
1762
+ "aria-selected": activeVariantId === "qoder",
1763
+ style: segmentedTabItemStyle(activeVariantId === "qoder"),
1764
+ onClick: () => setActiveVariantId("qoder"),
1765
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1766
+ style: dotStyle(cnDotStatus),
1767
+ "aria-hidden": "true"
1768
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("variantTabCN") })]
1769
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1770
+ type: "button",
1771
+ role: "tab",
1772
+ "aria-selected": activeVariantId === "qoder-global",
1773
+ style: segmentedTabItemStyle(activeVariantId === "qoder-global"),
1774
+ onClick: () => setActiveVariantId("qoder-global"),
1775
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1776
+ style: dotStyle(globalDotStatus),
1777
+ "aria-hidden": "true"
1778
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("variantTabGlobal") })]
1779
+ })]
1780
+ })] }) : null,
1061
1781
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1062
1782
  style: quotaTitleStyle,
1063
1783
  children: t("accountHeading")
1064
1784
  }),
1065
1785
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1066
- style: rowStyle$1,
1786
+ style: rowStyle,
1067
1787
  children: [
1068
1788
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1069
1789
  style: statusStyle,
@@ -1100,7 +1820,7 @@ window.__ModuleLoader__.load({
1100
1820
  })] })
1101
1821
  ]
1102
1822
  }),
1103
- readFailure === void 0 || signedIn === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1823
+ readFailure === void 0 || signedInState === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1104
1824
  style: errorStyle,
1105
1825
  children: t("statusRefreshFailed", { message: readFailure })
1106
1826
  }),
@@ -1112,7 +1832,7 @@ window.__ModuleLoader__.load({
1112
1832
  children: t("jobTokenRefreshed", { time: formatTime(status.jobTokenRefreshedAt) })
1113
1833
  }),
1114
1834
  status.catalog === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1115
- style: rowStyle$1,
1835
+ style: rowStyle,
1116
1836
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1117
1837
  style: bodyStyle,
1118
1838
  children: status.catalog.source === "live" && status.catalog.fetchedAt !== void 0 ? t("catalogLive", { time: formatTime(status.catalog.fetchedAt) }) : status.catalog.source === "saved" && status.catalog.fetchedAt !== void 0 ? t("catalogSaved", { time: formatTime(status.catalog.fetchedAt) }) : t("catalogFallback")
@@ -1136,7 +1856,8 @@ window.__ModuleLoader__.load({
1136
1856
  children: [
1137
1857
  "status",
1138
1858
  "context",
1139
- "details"
1859
+ "details",
1860
+ "checkin"
1140
1861
  ].map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1141
1862
  type: "button",
1142
1863
  role: "tab",
@@ -1148,7 +1869,7 @@ window.__ModuleLoader__.load({
1148
1869
  ...tabStyle,
1149
1870
  ...tab === id ? tabActiveStyle : {}
1150
1871
  },
1151
- children: t(id === "status" ? "tabStatus" : id === "context" ? "tabContext" : "tabDetails")
1872
+ children: t(id === "status" ? "tabStatus" : id === "context" ? "tabContext" : id === "details" ? "tabDetails" : "tabCheckIn")
1152
1873
  }, id))
1153
1874
  }),
1154
1875
  tab === "status" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1156,7 +1877,7 @@ window.__ModuleLoader__.load({
1156
1877
  children: [status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1157
1878
  style: quotaListStyle,
1158
1879
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1159
- style: rowStyle$1,
1880
+ style: rowStyle,
1160
1881
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1161
1882
  style: quotaTitleStyle,
1162
1883
  children: t("creditsHeading")
@@ -1186,7 +1907,7 @@ window.__ModuleLoader__.load({
1186
1907
  });
1187
1908
  }
1188
1909
  })
1189
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1910
+ }) : tab === "details" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1190
1911
  style: tabPanelStyle,
1191
1912
  children: status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1192
1913
  style: quotaListStyle,
@@ -1202,11 +1923,31 @@ window.__ModuleLoader__.load({
1202
1923
  t
1203
1924
  }, `${account.packageName}-${String(index)}`))]
1204
1925
  })
1926
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1927
+ style: tabPanelStyle,
1928
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckInLogTable, {
1929
+ logs: status.checkIn?.logs,
1930
+ t,
1931
+ busy,
1932
+ checkingIn,
1933
+ clearing: clearingLogs,
1934
+ disabled: status.status !== "signed-in",
1935
+ ...checkInNotice === void 0 ? {} : { notice: checkInNotice },
1936
+ onCheckIn: () => {
1937
+ manualCheckIn();
1938
+ },
1939
+ onRefresh: () => {
1940
+ manualRefresh();
1941
+ },
1942
+ onClear: () => {
1943
+ clearCheckInLogs();
1944
+ }
1945
+ })
1205
1946
  })
1206
1947
  ] }) : null,
1207
1948
  status?.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1208
1949
  style: status.reason === void 0 ? bodyStyle : errorStyle,
1209
- children: status.reason ?? t(variant.signedOutKey)
1950
+ children: status.reason ?? t(currentVariant.signedOutKey)
1210
1951
  }), authKey === void 0 ? null : patEntry()] }) : null,
1211
1952
  status?.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1212
1953
  style: errorStyle,
@@ -1278,7 +2019,7 @@ window.__ModuleLoader__.load({
1278
2019
  * it names the feature, so it should read as an annotation attached to the icon
1279
2020
  * rather than compete with the adjacent model selector.
1280
2021
  */
1281
- const labelStyle$1 = {
2022
+ const labelStyle = {
1282
2023
  fontSize: 11,
1283
2024
  lineHeight: "16px",
1284
2025
  color: "var(--dsw-alias-label-tertiary)"
@@ -1288,918 +2029,364 @@ window.__ModuleLoader__.load({
1288
2029
  position: "absolute",
1289
2030
  left: "50%",
1290
2031
  bottom: "calc(100% + 8px)",
1291
- zIndex: 1e3,
1292
- transform: "translateX(-50%)",
1293
- padding: "4px 8px",
1294
- borderRadius: 6,
1295
- background: "var(--dsw-specific-tip, #1f2329)",
1296
- boxShadow: "var(--dsw-shadow-lv2)",
1297
- color: "var(--dsw-alias-label-primary, #fff)",
1298
- fontSize: 12,
1299
- lineHeight: "18px",
1300
- whiteSpace: "nowrap",
1301
- pointerEvents: "none"
1302
- };
1303
- /** Confirmation bubble: same anchor, but interactive and allowed to wrap. */
1304
- const confirmStyle = {
1305
- position: "absolute",
1306
- right: 0,
1307
- bottom: "calc(100% + 8px)",
1308
- zIndex: 1001,
1309
- display: "flex",
1310
- flexDirection: "column",
1311
- gap: 8,
1312
- width: 260,
1313
- padding: "10px 12px",
1314
- border: "1px solid var(--dsw-alias-border-l2)",
1315
- borderRadius: 8,
1316
- background: "var(--dsw-alias-bg-layer-1, #fff)",
1317
- boxShadow: "var(--dsw-shadow-lv2)",
1318
- color: "var(--dsw-alias-label-primary)",
1319
- fontSize: 12,
1320
- lineHeight: "18px"
1321
- };
1322
- const confirmRowStyle = {
1323
- display: "flex",
1324
- justifyContent: "flex-end",
1325
- gap: 8
1326
- };
1327
- const confirmButtonStyle = {
1328
- padding: "3px 10px",
1329
- border: "1px solid var(--dsw-alias-border-l2)",
1330
- borderRadius: 6,
1331
- background: "transparent",
1332
- color: "inherit",
1333
- font: "inherit",
1334
- fontSize: 12,
1335
- cursor: "pointer"
1336
- };
1337
- /**
1338
- * Primary action inside the confirmation bubble.
1339
- *
1340
- * The fill and its text colour must come as a pair: `brand-primary` resolves to
1341
- * a light accent in this theme, so hardcoding `color: #fff` on top of it renders
1342
- * white-on-white. `button-primary-fill` + `label-primary-foreground` is the
1343
- * theme's own pair for exactly this, and is what `dsh-codex-connect` uses for
1344
- * the same job.
1345
- */
1346
- const primaryButtonStyle = {
1347
- ...confirmButtonStyle,
1348
- border: "1px solid var(--dsw-alias-button-primary-fill)",
1349
- background: "var(--dsw-alias-button-primary-fill)",
1350
- color: "var(--dsw-alias-label-primary-foreground)"
1351
- };
1352
- /**
1353
- * Result note: a single line + a dismiss button, anchored to the control's
1354
- * right side. Smaller than the confirmation bubble because it carries an
1355
- * *outcome*, not a *decision* — the work is done, the user only has to read
1356
- * and dismiss.
1357
- */
1358
- const noteStyle = {
1359
- position: "absolute",
1360
- right: 0,
1361
- bottom: "calc(100% + 8px)",
1362
- zIndex: 1001,
1363
- display: "flex",
1364
- alignItems: "center",
1365
- gap: 12,
1366
- padding: "6px 10px",
1367
- border: "1px solid var(--dsw-alias-border-l2)",
1368
- borderRadius: 8,
1369
- background: "var(--dsw-alias-bg-layer-1)",
1370
- boxShadow: "var(--dsw-shadow-lv2)",
1371
- color: "var(--dsw-alias-label-primary)",
1372
- fontSize: 12,
1373
- lineHeight: "18px",
1374
- whiteSpace: "nowrap"
1375
- };
1376
- /**
1377
- * The note's dismiss action. Outlined rather than bare text: inside an already
1378
- * bordered bubble, an unbordered word does not read as something you can click.
1379
- * Matches the outlined pill convention the plugin's other secondary actions use.
1380
- */
1381
- const noteDismissStyle = {
1382
- padding: "2px 8px",
1383
- border: "1px solid var(--dsw-alias-border-l2)",
1384
- borderRadius: 6,
1385
- background: "transparent",
1386
- color: "var(--dsw-alias-label-secondary)",
1387
- font: "inherit",
1388
- fontSize: 12,
1389
- lineHeight: "18px",
1390
- cursor: "pointer"
1391
- };
1392
- /**
1393
- * The feature's static inline label. Deliberately not a state readout — see the
1394
- * module comment.
1395
- */
1396
- function useLabel(t) {
1397
- return t("probeLabel");
1398
- }
1399
- /** Pick the model's recorded observation out of the probe section. */
1400
- function resultFor(status, model) {
1401
- if (status.status !== "signed-in") return void 0;
1402
- return status.probe?.results.find((result) => result.id === model);
1403
- }
1404
- /**
1405
- * The one-line tooltip: current state first, then what a click does — the same
1406
- * two-part shape Fast Mode uses.
1407
- *
1408
- * A recorded result outranks a remembered failure. `failed` only means "the last
1409
- * run from this control did not complete"; the host can record a result for the
1410
- * same model at any time (a detection started from the settings card, another
1411
- * conversation, or a finished sweep), and the levels the user paid for are the
1412
- * more useful answer than the stale failure. Failure copy is what remains when
1413
- * there is no result to report.
1414
- */
1415
- function tooltipText(t, model, state) {
1416
- if (state.busy) return t("probeRunning", { model });
1417
- const result = state.result;
1418
- if (result !== void 0) {
1419
- if (result.validation === "validating" && result.efforts.length > 0) return t("probeTooltipVerified", { levels: result.efforts.join(" / ") });
1420
- if (result.validation === "non-validating") return t("probeTooltipNotValidating");
1421
- return t("probeTooltipRetry");
1422
- }
1423
- if (state.failed) return t("probeTooltipRetry");
1424
- return t("probeTooltipIdle", { model });
1425
- }
1426
- /** Model-independent shell: resolves the selection, then delegates per model. */
1427
- function QoderProbeControl({ directory, t }) {
1428
- const subscribe = (0, react.useCallback)((listener) => directory.subscribe(listener), [directory]);
1429
- const snapshot = (0, react.useCallback)(() => directory.getSnapshot(), [directory]);
1430
- const selection = (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot).current;
1431
- const card = selection == null ? void 0 : cardVariantFor(selection.provider);
1432
- const key = card === void 0 || selection == null ? void 0 : `${card.id}:${selection.model}`;
1433
- return card === void 0 || selection == null || key === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelProbe, {
1434
- model: selection.model,
1435
- card,
1436
- label: useLabel(t),
1437
- t
1438
- }, key);
1439
- }
1440
- function ModelProbe({ model, card, label, t }) {
1441
- const [status, setStatus] = (0, react.useState)();
1442
- const [busy, setBusy] = (0, react.useState)(false);
1443
- const [confirming, setConfirming] = (0, react.useState)(false);
1444
- const [tooltipVisible, setTooltipVisible] = (0, react.useState)(false);
1445
- const [failed, setFailed] = (0, react.useState)(false);
1446
- const [note, setNote] = (0, react.useState)();
1447
- const inFlight = (0, react.useRef)(false);
1448
- const mounted = (0, react.useRef)(false);
1449
- const readSeq = (0, react.useRef)(0);
1450
- const tooltipId = (0, react.useId)();
1451
- const refresh = (0, react.useCallback)(async (signal) => {
1452
- const seq = ++readSeq.current;
1453
- const response = await fetch(card.statusPath, {
1454
- credentials: "same-origin",
1455
- headers: { accept: "application/json" },
1456
- ...signal === void 0 ? {} : { signal }
1457
- });
1458
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1459
- const value = await response.json().catch(() => void 0);
1460
- if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
1461
- if (mounted.current && !signal?.aborted && seq === readSeq.current) setStatus(value);
1462
- }, [card.statusPath, t]);
1463
- (0, react.useEffect)(() => {
1464
- mounted.current = true;
1465
- const controller = new AbortController();
1466
- const load = () => {
1467
- refresh(controller.signal).catch(() => {});
1468
- };
1469
- load();
1470
- const timer = window.setInterval(load, RECONCILE_MS);
1471
- window.addEventListener("focus", load);
1472
- return () => {
1473
- mounted.current = false;
1474
- controller.abort();
1475
- window.clearInterval(timer);
1476
- window.removeEventListener("focus", load);
1477
- };
1478
- }, [refresh]);
1479
- const probe = status?.status === "signed-in" ? status.probe : void 0;
1480
- const key = status?.status === "signed-in" ? status.probeKey : void 0;
1481
- const result = status === void 0 ? void 0 : resultFor(status, model);
1482
- const visible = probe?.candidates.includes(model) === true || result !== void 0;
1483
- (0, react.useEffect)(() => {
1484
- if (result !== void 0) setFailed(false);
1485
- }, [result]);
1486
- (0, react.useEffect)(() => {
1487
- setConfirming(false);
1488
- setNote(void 0);
1489
- }, [model]);
1490
- const detect = async () => {
1491
- if (key === void 0 || inFlight.current || probe?.running === true) return;
1492
- inFlight.current = true;
1493
- setNote(void 0);
1494
- setConfirming(false);
1495
- setBusy(true);
1496
- setFailed(false);
1497
- try {
1498
- const response = await fetch(card.probePath, {
1499
- method: "POST",
1500
- credentials: "same-origin",
1501
- headers: {
1502
- "Content-Type": "application/json",
1503
- "X-Qoder-Probe-Key": key
1504
- },
1505
- body: JSON.stringify({
1506
- action: "probe",
1507
- model
1508
- })
1509
- });
1510
- const body = await response.json();
1511
- if (!response.ok || body.state !== "ok" || body.validation !== "validating" && body.validation !== "non-validating" || !Array.isArray(body.efforts) || !body.efforts.every((effort) => typeof effort === "string")) throw new Error("probe failed");
1512
- if (mounted.current) {
1513
- const completed = {
1514
- id: model,
1515
- name: model,
1516
- validation: body.validation,
1517
- efforts: body.efforts,
1518
- probedAt: Date.now()
1519
- };
1520
- setNote(completed);
1521
- }
1522
- refresh().catch(() => {});
1523
- } catch {
1524
- if (mounted.current) setFailed(true);
1525
- } finally {
1526
- inFlight.current = false;
1527
- if (mounted.current) setBusy(false);
1528
- }
1529
- };
1530
- if (!visible) return null;
1531
- const text = tooltipText(t, model, {
1532
- busy,
1533
- result,
1534
- failed
1535
- });
1536
- const disabled = busy || probe?.running === true || key === void 0;
1537
- const showTooltip = tooltipVisible && !confirming && note === void 0;
1538
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1539
- style: wrapperStyle,
1540
- onMouseEnter: () => {
1541
- setTooltipVisible(true);
1542
- },
1543
- onMouseLeave: () => {
1544
- setTooltipVisible(false);
1545
- },
1546
- children: [
1547
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1548
- type: "button",
1549
- "aria-label": text,
1550
- "aria-describedby": showTooltip ? tooltipId : void 0,
1551
- "aria-busy": busy,
1552
- "aria-expanded": confirming,
1553
- disabled,
1554
- onClick: () => {
1555
- setConfirming(true);
1556
- },
1557
- onFocus: () => {
1558
- setTooltipVisible(true);
1559
- },
1560
- onBlur: () => {
1561
- setTooltipVisible(false);
1562
- },
1563
- style: {
1564
- ...buttonStyle,
1565
- opacity: disabled && !confirming ? .6 : 1,
1566
- cursor: disabled ? "default" : "pointer"
1567
- },
1568
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1569
- width: "16",
1570
- height: "16",
1571
- viewBox: "0 0 24 24",
1572
- fill: "none",
1573
- stroke: "currentColor",
1574
- strokeWidth: "1.6",
1575
- "aria-hidden": "true",
1576
- focusable: "false",
1577
- children: [
1578
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1579
- cx: "12",
1580
- cy: "12",
1581
- r: "9"
1582
- }),
1583
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1584
- cx: "12",
1585
- cy: "12",
1586
- r: "4"
1587
- }),
1588
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 12 20 4" }),
1589
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1590
- cx: "12",
1591
- cy: "12",
1592
- r: "1"
1593
- })
1594
- ]
1595
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1596
- style: labelStyle$1,
1597
- children: label
1598
- })]
1599
- }),
1600
- showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1601
- id: tooltipId,
1602
- role: "tooltip",
1603
- style: tooltipStyle,
1604
- children: text
1605
- }),
1606
- confirming && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1607
- style: confirmStyle,
1608
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("probeBubbleBody") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1609
- style: confirmRowStyle,
1610
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1611
- type: "button",
1612
- style: confirmButtonStyle,
1613
- onClick: () => {
1614
- setConfirming(false);
1615
- },
1616
- children: t("cancel")
1617
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1618
- type: "button",
1619
- style: primaryButtonStyle,
1620
- onClick: () => {
1621
- detect();
1622
- },
1623
- children: t("probeConfirmAction")
1624
- })]
1625
- })]
1626
- }),
1627
- note === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1628
- role: "status",
1629
- "aria-live": "polite",
1630
- style: noteStyle,
1631
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: noteText(t, note) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1632
- type: "button",
1633
- style: noteDismissStyle,
1634
- onClick: () => {
1635
- setNote(void 0);
1636
- },
1637
- children: t("probeNoteDismiss")
1638
- })]
1639
- })
1640
- ]
1641
- });
1642
- }
1643
- /** Compose the one-line outcome string the note bubble shows. */
1644
- function noteText(t, result) {
1645
- if (result.validation === "validating" && result.efforts.length > 0) return t("probeNoteVerified", { levels: result.efforts.join(" / ") });
1646
- if (result.validation === "non-validating") return t("probeNoteNotValidating");
1647
- return t("probeNoteUnknown");
1648
- }
1649
- //#endregion
1650
- //#region src/client/quota-settings-store.ts
1651
- let pollIntervalMs = 3e5;
1652
- const toggles = {
1653
- cn: false,
1654
- global: false
2032
+ zIndex: 1e3,
2033
+ transform: "translateX(-50%)",
2034
+ padding: "4px 8px",
2035
+ borderRadius: 6,
2036
+ background: "var(--dsw-specific-tip, #1f2329)",
2037
+ boxShadow: "var(--dsw-shadow-lv2)",
2038
+ color: "var(--dsw-alias-label-primary, #fff)",
2039
+ fontSize: 12,
2040
+ lineHeight: "18px",
2041
+ whiteSpace: "nowrap",
2042
+ pointerEvents: "none"
1655
2043
  };
1656
- const signIn = {
1657
- cn: false,
1658
- global: false
2044
+ /** Confirmation bubble: same anchor, but interactive and allowed to wrap. */
2045
+ const confirmStyle = {
2046
+ position: "absolute",
2047
+ right: 0,
2048
+ bottom: "calc(100% + 8px)",
2049
+ zIndex: 1001,
2050
+ display: "flex",
2051
+ flexDirection: "column",
2052
+ gap: 8,
2053
+ width: 260,
2054
+ padding: "10px 12px",
2055
+ border: "1px solid var(--dsw-alias-border-l2)",
2056
+ borderRadius: 8,
2057
+ background: "var(--dsw-alias-bg-layer-1, #fff)",
2058
+ boxShadow: "var(--dsw-shadow-lv2)",
2059
+ color: "var(--dsw-alias-label-primary)",
2060
+ fontSize: 12,
2061
+ lineHeight: "18px"
2062
+ };
2063
+ const confirmRowStyle = {
2064
+ display: "flex",
2065
+ justifyContent: "flex-end",
2066
+ gap: 8
2067
+ };
2068
+ const confirmButtonStyle = {
2069
+ padding: "3px 10px",
2070
+ border: "1px solid var(--dsw-alias-border-l2)",
2071
+ borderRadius: 6,
2072
+ background: "transparent",
2073
+ color: "inherit",
2074
+ font: "inherit",
2075
+ fontSize: 12,
2076
+ cursor: "pointer"
1659
2077
  };
1660
- let revision = 0;
1661
- const listeners = /* @__PURE__ */ new Set();
1662
- function bump() {
1663
- revision += 1;
1664
- for (const listener of listeners) listener();
1665
- }
1666
- /** Update the shared poll interval (from the settings document). */
1667
- function setQuotaPollMs(ms) {
1668
- if (Number.isFinite(ms) && ms >= 6e4 && pollIntervalMs !== ms) {
1669
- pollIntervalMs = ms;
1670
- bump();
1671
- }
1672
- }
1673
- /** Read the configured poll interval. */
1674
- function quotaPollMs() {
1675
- return pollIntervalMs;
1676
- }
1677
- /** Update both sidebar toggles (from the settings document). */
1678
- function setQuotaToggles(cn, global) {
1679
- if (toggles.cn !== cn || toggles.global !== global) {
1680
- toggles.cn = cn;
1681
- toggles.global = global;
1682
- bump();
1683
- }
1684
- }
1685
- /** Read the current toggles. */
1686
- function quotaToggles() {
1687
- return { ...toggles };
1688
- }
1689
- /** Record a variant's sign-in state from any successful status poll. */
1690
- function noteQuotaSignIn(variantId, signedIn) {
1691
- if (variantId === "qoder" && signIn.cn !== signedIn) {
1692
- signIn.cn = signedIn;
1693
- bump();
1694
- } else if (variantId === "qoder-global" && signIn.global !== signedIn) {
1695
- signIn.global = signedIn;
1696
- bump();
1697
- }
1698
- }
1699
- /** Read the cached sign-in state. */
1700
- function quotaSignInState() {
1701
- return { ...signIn };
1702
- }
1703
- /** Subscribe to any flag change; returns the disposer. */
1704
- function onQuotaSettingsChange(listener) {
1705
- listeners.add(listener);
1706
- return () => {
1707
- listeners.delete(listener);
1708
- };
1709
- }
1710
- /** The current revision — the useSyncExternalStore snapshot value. */
1711
- function quotaSettingsRevision() {
1712
- return revision;
1713
- }
1714
- /** Which variant a status route belongs to, from the route path. */
1715
- function variantOfStatusPath(statusPath) {
1716
- return statusPath.includes("/global/") ? "qoder-global" : "qoder";
1717
- }
1718
2078
  /**
1719
- * The last status document per variant, shared by EVERY quota surface.
1720
- *
1721
- * The sidebar cards and the dashboard each used to fetch independently, so a
1722
- * dashboard refresh updated the panel while the sidebar card kept showing its
1723
- * previous read until its own next tick — two different numbers for one
1724
- * account on one screen. One store, one write path
1725
- * ({@link noteQuotaStatus}), and every subscriber re-renders through the
1726
- * same revision: whatever surface refreshed last, all of them show it.
2079
+ * Primary action inside the confirmation bubble.
1727
2080
  *
1728
- * Documents are kept by identity (never mutated), so reference comparisons
1729
- * in useSyncExternalStore selectors stay cheap and stable.
2081
+ * The fill and its text colour must come as a pair: `brand-primary` resolves to
2082
+ * a light accent in this theme, so hardcoding `color: #fff` on top of it renders
2083
+ * white-on-white. `button-primary-fill` + `label-primary-foreground` is the
2084
+ * theme's own pair for exactly this, and is what `dsh-codex-connect` uses for
2085
+ * the same job.
1730
2086
  */
1731
- const statusDocuments = {
1732
- cn: void 0,
1733
- global: void 0
2087
+ const primaryButtonStyle = {
2088
+ ...confirmButtonStyle,
2089
+ border: "1px solid var(--dsw-alias-button-primary-fill)",
2090
+ background: "var(--dsw-alias-button-primary-fill)",
2091
+ color: "var(--dsw-alias-label-primary-foreground)"
1734
2092
  };
1735
2093
  /**
1736
- * Publish one variant's freshly fetched status document. Downstream
1737
- * subscribers (both sidebar cards and the dashboard, through whichever
1738
- * observable wraps this store) re-render on the revision bump.
2094
+ * Result note: a single line + a dismiss button, anchored to the control's
2095
+ * right side. Smaller than the confirmation bubble because it carries an
2096
+ * *outcome*, not a *decision* — the work is done, the user only has to read
2097
+ * and dismiss.
1739
2098
  */
1740
- function noteQuotaStatus(variantId, status) {
1741
- if (variantId === "qoder" && statusDocuments.cn !== status) {
1742
- statusDocuments.cn = status;
1743
- statusFetchedAt.cn = Date.now();
1744
- bump();
1745
- } else if (variantId === "qoder-global" && statusDocuments.global !== status) {
1746
- statusDocuments.global = status;
1747
- statusFetchedAt.global = Date.now();
1748
- bump();
1749
- }
1750
- noteQuotaSignIn(variantId, status.status === "signed-in");
1751
- }
1752
- /** Read one variant's latest status document. */
1753
- function quotaStatus(variantId) {
1754
- return variantId === "qoder" ? statusDocuments.cn : statusDocuments.global;
1755
- }
1756
- /** When each variant's document was last fetched (per publish, not per read). */
1757
- const statusFetchedAt = {
1758
- cn: void 0,
1759
- global: void 0
2099
+ const noteStyle = {
2100
+ position: "absolute",
2101
+ right: 0,
2102
+ bottom: "calc(100% + 8px)",
2103
+ zIndex: 1001,
2104
+ display: "flex",
2105
+ alignItems: "center",
2106
+ gap: 12,
2107
+ padding: "6px 10px",
2108
+ border: "1px solid var(--dsw-alias-border-l2)",
2109
+ borderRadius: 8,
2110
+ background: "var(--dsw-alias-bg-layer-1)",
2111
+ boxShadow: "var(--dsw-shadow-lv2)",
2112
+ color: "var(--dsw-alias-label-primary)",
2113
+ fontSize: 12,
2114
+ lineHeight: "18px",
2115
+ whiteSpace: "nowrap"
1760
2116
  };
1761
- /** Read the time a variant's current document was fetched, if any. */
1762
- function quotaStatusFetchedAt(variantId) {
1763
- return variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
1764
- }
1765
2117
  /**
1766
- * Whether ONE variant's shared document is fresh enough to skip a fetch:
1767
- * the user's rule — a click/mount/interval tick within the configured
1768
- * interval of the last successful read reuses the cached document, and only
1769
- * a variant with NO result yet (or a failed read that never landed one)
1770
- * forces the upstream call. The interval is a cache lifetime, not a metronome.
1771
- *
1772
- * A `maxAgeMs` of the poll interval comes from the settings document; a
1773
- * failed last read is NOT tracked here (callers gate failures themselves),
1774
- * because "the last read failed" still means "no usable result".
1775
- *
1776
- * @param variantId - which variant's freshness to test.
1777
- * @param maxAgeMs - the configured poll interval (cache lifetime).
2118
+ * The note's dismiss action. Outlined rather than bare text: inside an already
2119
+ * bordered bubble, an unbordered word does not read as something you can click.
2120
+ * Matches the outlined pill convention the plugin's other secondary actions use.
1778
2121
  */
1779
- function quotaStatusIsFresh(variantId, maxAgeMs) {
1780
- const fetchedAt = variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
1781
- const document = variantId === "qoder" ? statusDocuments.cn : statusDocuments.global;
1782
- if (fetchedAt === void 0 || document === void 0) return false;
1783
- return Date.now() - fetchedAt < maxAgeMs;
1784
- }
1785
- //#endregion
1786
- //#region src/client/QuotaSettingsCard.tsx
2122
+ const noteDismissStyle = {
2123
+ padding: "2px 8px",
2124
+ border: "1px solid var(--dsw-alias-border-l2)",
2125
+ borderRadius: 6,
2126
+ background: "transparent",
2127
+ color: "var(--dsw-alias-label-secondary)",
2128
+ font: "inherit",
2129
+ fontSize: 12,
2130
+ lineHeight: "18px",
2131
+ cursor: "pointer"
2132
+ };
1787
2133
  /**
1788
- * The shared quota-settings card: one card above the two variant cards that
1789
- * configures both sidebar quota widgets.
1790
- *
1791
- * Like the built-in plugin cards, it registers into `settings.plugin.item`
1792
- * keyed by its own settings namespace (`qoder-quota`), binds that
1793
- * namespace through the client settings scope, and writes through the scope's
1794
- * revision-fenced `set` — the same durable-write path every preference row
1795
- * uses. A toggle commits on click: each click is one explicit user choice,
1796
- * and the scope's ordering makes the last one win, so no staged-draft form is
1797
- * needed for two booleans and a number.
1798
- *
1799
- * The two toggles gate the CN and international sidebar cards respectively;
1800
- * the interval is one shared poll period. Toggles are disabled while their
1801
- * variant is signed out: a quota card for an account nobody is signed into
1802
- * would render an error forever, so the setting waits for a session.
2134
+ * The feature's static inline label. Deliberately not a state readout — see the
2135
+ * module comment.
1803
2136
  */
1804
- /** The default poll interval shown before a value is stored. */
1805
- const POLL_DEFAULT_MS = 3e5;
1806
- /** Floor the schema also enforces; mirrored here for immediate UI feedback. */
1807
- const POLL_MIN_MS = 6e4;
1808
- /** Read the section values out of a scope snapshot (defaults when absent). */
1809
- function project(scope) {
1810
- if (scope === void 0) return {
1811
- status: "unavailable",
1812
- writable: false,
1813
- values: {
1814
- sidebarQuotaCN: false,
1815
- sidebarQuotaGlobal: false,
1816
- quotaPollMs: POLL_DEFAULT_MS
1817
- }
1818
- };
1819
- const snapshot = scope.getSnapshot();
1820
- const value = snapshot.value ?? {};
1821
- return {
1822
- status: snapshot.status,
1823
- writable: snapshot.writable,
1824
- values: {
1825
- sidebarQuotaCN: value.sidebarQuotaCN === true,
1826
- sidebarQuotaGlobal: value.sidebarQuotaGlobal === true,
1827
- quotaPollMs: typeof value.quotaPollMs === "number" ? value.quotaPollMs : POLL_DEFAULT_MS
1828
- }
1829
- };
2137
+ function useLabel(t) {
2138
+ return t("probeLabel");
2139
+ }
2140
+ /** Pick the model's recorded observation out of the probe section. */
2141
+ function resultFor(status, model) {
2142
+ if (status.status !== "signed-in") return void 0;
2143
+ return status.probe?.results.find((result) => result.id === model);
1830
2144
  }
1831
2145
  /**
1832
- * Stable-reference projection cache.
2146
+ * The one-line tooltip: current state first, then what a click does — the same
2147
+ * two-part shape Fast Mode uses.
1833
2148
  *
1834
- * React's useSyncExternalStore requires getSnapshot() to return THE SAME
1835
- * reference between renders unless the store actually changed. project()
1836
- * builds a fresh object every call, which re-renders forever and crashes the
1837
- * card with React error #185 ("maximum update depth exceeded") — exactly the
1838
- * crash the slot ledger reported. The cache below returns the last built
1839
- * projection until the underlying scope snapshot (or scope identity) changes,
1840
- * which is the only thing the projection actually derives from.
2149
+ * A recorded result outranks a remembered failure. `failed` only means "the last
2150
+ * run from this control did not complete"; the host can record a result for the
2151
+ * same model at any time (a detection started from the settings card, another
2152
+ * conversation, or a finished sweep), and the levels the user paid for are the
2153
+ * more useful answer than the stale failure. Failure copy is what remains when
2154
+ * there is no result to report.
1841
2155
  */
1842
- let cachedScope;
1843
- let cachedSource;
1844
- let cachedProjection;
1845
- const UNAVAILABLE = {
1846
- status: "unavailable",
1847
- writable: false,
1848
- values: {
1849
- sidebarQuotaCN: false,
1850
- sidebarQuotaGlobal: false,
1851
- quotaPollMs: POLL_DEFAULT_MS
1852
- }
1853
- };
1854
- function stableProject(scope) {
1855
- if (scope === void 0) return UNAVAILABLE;
1856
- const source = scope.getSnapshot();
1857
- if (scope !== cachedScope || source !== cachedSource || cachedProjection === void 0) {
1858
- cachedScope = scope;
1859
- cachedSource = source;
1860
- cachedProjection = project(scope);
2156
+ function tooltipText(t, model, state) {
2157
+ if (state.busy) return t("probeRunning", { model });
2158
+ const result = state.result;
2159
+ if (result !== void 0) {
2160
+ if (result.validation === "validating" && result.efforts.length > 0) return t("probeTooltipVerified", { levels: result.efforts.join(" / ") });
2161
+ if (result.validation === "non-validating") return t("probeTooltipNotValidating");
2162
+ return t("probeTooltipRetry");
1861
2163
  }
1862
- return cachedProjection;
2164
+ if (state.failed) return t("probeTooltipRetry");
2165
+ return t("probeTooltipIdle", { model });
1863
2166
  }
1864
- /** One toggle row: label, hint, and a switch drawn to the shell's proportions. */
1865
- function ToggleRow({ label, hint, checked, disabled, disabledHint, onToggle }) {
1866
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1867
- style: rowStyle,
1868
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1869
- style: rowTextStyle,
1870
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1871
- style: labelStyle,
1872
- children: label
1873
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1874
- style: hintStyle,
1875
- children: disabled === true && disabledHint !== void 0 ? disabledHint : hint
1876
- })]
1877
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1878
- type: "button",
1879
- role: "switch",
1880
- "aria-checked": checked,
1881
- disabled,
1882
- "aria-label": label,
1883
- onClick: () => onToggle(!checked),
1884
- style: {
1885
- ...switchStyle,
1886
- background: checked ? "var(--dsw-alias-brand-primary)" : "var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.2))",
1887
- justifyContent: checked ? "flex-end" : "flex-start",
1888
- opacity: disabled === true ? .45 : 1,
1889
- cursor: disabled === true ? "not-allowed" : "pointer"
1890
- },
1891
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: knobStyle })
1892
- })]
1893
- });
2167
+ /** Model-independent shell: resolves the selection, then delegates per model. */
2168
+ function QoderProbeControl({ directory, t }) {
2169
+ const subscribe = (0, react.useCallback)((listener) => directory.subscribe(listener), [directory]);
2170
+ const snapshot = (0, react.useCallback)(() => directory.getSnapshot(), [directory]);
2171
+ const selection = (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot).current;
2172
+ const card = selection == null ? void 0 : cardVariantFor(selection.provider);
2173
+ const key = card === void 0 || selection == null ? void 0 : `${card.id}:${selection.model}`;
2174
+ return card === void 0 || selection == null || key === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelProbe, {
2175
+ model: selection.model,
2176
+ card,
2177
+ label: useLabel(t),
2178
+ t
2179
+ }, key);
1894
2180
  }
1895
- /** The shared quota-settings card. */
1896
- function QuotaSettingsCard(props) {
1897
- const { t = (key) => key, scope, signedIn } = props;
1898
- const projection = (0, react.useSyncExternalStore)(scope?.subscribe.bind(scope) ?? ((listener) => listener), () => stableProject(scope));
1899
- const [open, setOpen] = (0, react.useState)(false);
1900
- const [hovered, setHovered] = (0, react.useState)(false);
1901
- const [headerFocused, setHeaderFocused] = (0, react.useState)(false);
1902
- const [probe, setProbe] = (0, react.useState)();
2181
+ function ModelProbe({ model, card, label, t }) {
2182
+ const [status, setStatus] = (0, react.useState)();
2183
+ const [busy, setBusy] = (0, react.useState)(false);
2184
+ const [confirming, setConfirming] = (0, react.useState)(false);
2185
+ const [tooltipVisible, setTooltipVisible] = (0, react.useState)(false);
2186
+ const [failed, setFailed] = (0, react.useState)(false);
2187
+ const [note, setNote] = (0, react.useState)();
2188
+ const inFlight = (0, react.useRef)(false);
2189
+ const mounted = (0, react.useRef)(false);
2190
+ const readSeq = (0, react.useRef)(0);
2191
+ const tooltipId = (0, react.useId)();
2192
+ const refresh = (0, react.useCallback)(async (signal) => {
2193
+ const seq = ++readSeq.current;
2194
+ const response = await fetch(card.statusPath, {
2195
+ credentials: "same-origin",
2196
+ headers: { accept: "application/json" },
2197
+ ...signal === void 0 ? {} : { signal }
2198
+ });
2199
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
2200
+ const value = await response.json().catch(() => void 0);
2201
+ if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
2202
+ if (mounted.current && !signal?.aborted && seq === readSeq.current) setStatus(value);
2203
+ }, [card.statusPath, t]);
1903
2204
  (0, react.useEffect)(() => {
1904
- let disposed = false;
1905
- const probeOne = async (path) => {
1906
- try {
1907
- const body = await (await fetch(path, { headers: { accept: "application/json" } })).json();
1908
- if (disposed || !isQoderWebStatus(body)) return void 0;
1909
- noteQuotaSignIn(variantOfStatusPath(path), body.status === "signed-in");
1910
- return body.status === "signed-in";
1911
- } catch {
1912
- return;
1913
- }
2205
+ mounted.current = true;
2206
+ const controller = new AbortController();
2207
+ const load = () => {
2208
+ refresh(controller.signal).catch(() => {});
1914
2209
  };
1915
- (async () => {
1916
- const [cn, global] = await Promise.all([probeOne(QODER_STATUS_PATH), probeOne(QODER_GLOBAL_STATUS_PATH)]);
1917
- if (!disposed) setProbe({
1918
- cn: cn === true,
1919
- global: global === true
1920
- });
1921
- })();
2210
+ load();
2211
+ const timer = window.setInterval(load, RECONCILE_MS);
2212
+ window.addEventListener("focus", load);
1922
2213
  return () => {
1923
- disposed = true;
2214
+ mounted.current = false;
2215
+ controller.abort();
2216
+ window.clearInterval(timer);
2217
+ window.removeEventListener("focus", load);
1924
2218
  };
1925
- }, [open]);
1926
- if (projection.status === "unavailable") return null;
1927
- const reported = signedIn?.() ?? {
1928
- cn: false,
1929
- global: false
1930
- };
1931
- const signed = {
1932
- cn: probe?.cn ?? reported.cn,
1933
- global: probe?.global ?? reported.global
1934
- };
1935
- const write = (field, value) => {
1936
- scope?.set(field, value);
2219
+ }, [refresh]);
2220
+ const probe = status?.status === "signed-in" ? status.probe : void 0;
2221
+ const key = status?.status === "signed-in" ? status.probeKey : void 0;
2222
+ const result = status === void 0 ? void 0 : resultFor(status, model);
2223
+ const visible = probe?.candidates.includes(model) === true || result !== void 0;
2224
+ (0, react.useEffect)(() => {
2225
+ if (result !== void 0) setFailed(false);
2226
+ }, [result]);
2227
+ (0, react.useEffect)(() => {
2228
+ setConfirming(false);
2229
+ setNote(void 0);
2230
+ }, [model]);
2231
+ const detect = async () => {
2232
+ if (key === void 0 || inFlight.current || probe?.running === true) return;
2233
+ inFlight.current = true;
2234
+ setNote(void 0);
2235
+ setConfirming(false);
2236
+ setBusy(true);
2237
+ setFailed(false);
2238
+ try {
2239
+ const response = await fetch(card.probePath, {
2240
+ method: "POST",
2241
+ credentials: "same-origin",
2242
+ headers: {
2243
+ "Content-Type": "application/json",
2244
+ "X-Qoder-Probe-Key": key
2245
+ },
2246
+ body: JSON.stringify({
2247
+ action: "probe",
2248
+ model
2249
+ })
2250
+ });
2251
+ const body = await response.json();
2252
+ if (!response.ok || body.state !== "ok" || body.validation !== "validating" && body.validation !== "non-validating" || !Array.isArray(body.efforts) || !body.efforts.every((effort) => typeof effort === "string")) throw new Error("probe failed");
2253
+ if (mounted.current) {
2254
+ const completed = {
2255
+ id: model,
2256
+ name: model,
2257
+ validation: body.validation,
2258
+ efforts: body.efforts,
2259
+ probedAt: Date.now()
2260
+ };
2261
+ setNote(completed);
2262
+ }
2263
+ refresh().catch(() => {});
2264
+ } catch {
2265
+ if (mounted.current) setFailed(true);
2266
+ } finally {
2267
+ inFlight.current = false;
2268
+ if (mounted.current) setBusy(false);
2269
+ }
1937
2270
  };
1938
- const minutes = Math.max(POLL_MIN_MS / 6e4, Math.round(projection.values.quotaPollMs / 6e4));
1939
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1940
- style: {
1941
- ...cardStyle,
1942
- ...hovered ? cardHoverStyle : {},
1943
- ...open ? cardOpenStyle : {}
1944
- },
2271
+ if (!visible) return null;
2272
+ const text = tooltipText(t, model, {
2273
+ busy,
2274
+ result,
2275
+ failed
2276
+ });
2277
+ const disabled = busy || probe?.running === true || key === void 0;
2278
+ const showTooltip = tooltipVisible && !confirming && note === void 0;
2279
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2280
+ style: wrapperStyle,
1945
2281
  onMouseEnter: () => {
1946
- setHovered(true);
2282
+ setTooltipVisible(true);
1947
2283
  },
1948
2284
  onMouseLeave: () => {
1949
- setHovered(false);
2285
+ setTooltipVisible(false);
1950
2286
  },
1951
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1952
- type: "button",
1953
- "aria-expanded": open,
1954
- "aria-label": `${t(open ? "collapse" : "expand")}: ${t("quotaSettingsTitle")}`,
1955
- onClick: () => setOpen((o) => !o),
1956
- onFocus: (event) => {
1957
- let keyboard = true;
1958
- try {
1959
- keyboard = event.currentTarget.matches(":focus-visible");
1960
- } catch {
1961
- keyboard = true;
1962
- }
1963
- if (keyboard) setHeaderFocused(true);
1964
- },
1965
- onBlur: () => {
1966
- setHeaderFocused(false);
1967
- },
1968
- style: {
1969
- ...headerButtonStyle,
1970
- ...headerFocused ? headerFocusStyle : {}
1971
- },
1972
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1973
- style: headTextStyle,
1974
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1975
- style: titleStyle,
1976
- children: t("quotaSettingsTitle")
1977
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1978
- style: introStyle,
1979
- children: t("quotaSettingsIntro")
1980
- })]
1981
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2287
+ children: [
2288
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2289
+ type: "button",
2290
+ "aria-label": text,
2291
+ "aria-describedby": showTooltip ? tooltipId : void 0,
2292
+ "aria-busy": busy,
2293
+ "aria-expanded": confirming,
2294
+ disabled,
2295
+ onClick: () => {
2296
+ setConfirming(true);
2297
+ },
2298
+ onFocus: () => {
2299
+ setTooltipVisible(true);
2300
+ },
2301
+ onBlur: () => {
2302
+ setTooltipVisible(false);
2303
+ },
1982
2304
  style: {
1983
- ...chevronStyle,
1984
- transform: open ? "rotate(180deg)" : "none"
2305
+ ...buttonStyle,
2306
+ opacity: disabled && !confirming ? .6 : 1,
2307
+ cursor: disabled ? "default" : "pointer"
1985
2308
  },
1986
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, {})
1987
- })]
1988
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1989
- style: cardBodyStyle,
1990
- children: [
1991
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
1992
- label: t("quotaToggleCN"),
1993
- hint: t("quotaToggleHint"),
1994
- checked: projection.values.sidebarQuotaCN,
1995
- disabled: !signed.cn,
1996
- disabledHint: t("quotaSignInRequired"),
1997
- onToggle: (next) => write("sidebarQuotaCN", next)
1998
- }),
1999
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
2000
- label: t("quotaToggleGlobal"),
2001
- hint: t("quotaToggleHint"),
2002
- checked: projection.values.sidebarQuotaGlobal,
2003
- disabled: !signed.global,
2004
- disabledHint: t("quotaSignInRequired"),
2005
- onToggle: (next) => write("sidebarQuotaGlobal", next)
2006
- }),
2007
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2008
- style: {
2009
- ...rowStyle,
2010
- borderBottom: "none",
2011
- paddingBottom: 0
2012
- },
2013
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2014
- style: rowTextStyle,
2015
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2016
- style: labelStyle,
2017
- children: t("quotaPollLabel")
2018
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2019
- style: hintStyle,
2020
- children: t("quotaPollHint")
2021
- })]
2022
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2023
- style: pollFieldStyle,
2024
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2025
- type: "number",
2026
- min: POLL_MIN_MS / 6e4,
2027
- step: 1,
2028
- value: minutes,
2029
- "aria-label": t("quotaPollLabel"),
2030
- onChange: (event) => {
2031
- const mins = Number.parseInt(event.target.value, 10);
2032
- if (Number.isFinite(mins) && mins > 0) write("quotaPollMs", Math.max(POLL_MIN_MS, mins * 6e4));
2033
- },
2034
- style: inputStyle
2035
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2036
- style: hintStyle,
2037
- children: t("quotaPollUnit")
2038
- })]
2309
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2310
+ width: "16",
2311
+ height: "16",
2312
+ viewBox: "0 0 24 24",
2313
+ fill: "none",
2314
+ stroke: "currentColor",
2315
+ strokeWidth: "1.6",
2316
+ "aria-hidden": "true",
2317
+ focusable: "false",
2318
+ children: [
2319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2320
+ cx: "12",
2321
+ cy: "12",
2322
+ r: "9"
2323
+ }),
2324
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2325
+ cx: "12",
2326
+ cy: "12",
2327
+ r: "4"
2328
+ }),
2329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 12 20 4" }),
2330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2331
+ cx: "12",
2332
+ cy: "12",
2333
+ r: "1"
2334
+ })
2335
+ ]
2336
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2337
+ style: labelStyle,
2338
+ children: label
2339
+ })]
2340
+ }),
2341
+ showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2342
+ id: tooltipId,
2343
+ role: "tooltip",
2344
+ style: tooltipStyle,
2345
+ children: text
2346
+ }),
2347
+ confirming && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2348
+ style: confirmStyle,
2349
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("probeBubbleBody") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2350
+ style: confirmRowStyle,
2351
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2352
+ type: "button",
2353
+ style: confirmButtonStyle,
2354
+ onClick: () => {
2355
+ setConfirming(false);
2356
+ },
2357
+ children: t("cancel")
2358
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2359
+ type: "button",
2360
+ style: primaryButtonStyle,
2361
+ onClick: () => {
2362
+ detect();
2363
+ },
2364
+ children: t("probeConfirmAction")
2039
2365
  })]
2040
- }),
2041
- projection.writable === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2042
- style: hintStyle,
2043
- children: t("quotaSettingsSaveFailed")
2044
- }) : null
2045
- ]
2046
- }) : null]
2366
+ })]
2367
+ }),
2368
+ note === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2369
+ role: "status",
2370
+ "aria-live": "polite",
2371
+ style: noteStyle,
2372
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: noteText(t, note) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2373
+ type: "button",
2374
+ style: noteDismissStyle,
2375
+ onClick: () => {
2376
+ setNote(void 0);
2377
+ },
2378
+ children: t("probeNoteDismiss")
2379
+ })]
2380
+ })
2381
+ ]
2047
2382
  });
2048
2383
  }
2049
- /** The disclosure chevron, drawn from the same path data as the variant cards'. */
2050
- function ChevronDownIcon() {
2051
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2052
- width: 14,
2053
- height: 14,
2054
- viewBox: "0 0 14 14",
2055
- fill: "none",
2056
- xmlns: "http://www.w3.org/2000/svg",
2057
- "aria-hidden": "true",
2058
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2059
- d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2060
- fill: "currentColor"
2061
- })
2062
- });
2384
+ /** Compose the one-line outcome string the note bubble shows. */
2385
+ function noteText(t, result) {
2386
+ if (result.validation === "validating" && result.efforts.length > 0) return t("probeNoteVerified", { levels: result.efforts.join(" / ") });
2387
+ if (result.validation === "non-validating") return t("probeNoteNotValidating");
2388
+ return t("probeNoteUnknown");
2063
2389
  }
2064
- const cardStyle = {
2065
- listStyle: "none",
2066
- borderWidth: "0.5px",
2067
- borderStyle: "solid",
2068
- borderColor: "var(--dsw-alias-border-l4)",
2069
- borderRadius: 16,
2070
- background: "var(--dsw-alias-bg-layer-3)",
2071
- transition: "border-color .16s, background .16s"
2072
- };
2073
- /** Hover, matching the built-in card's `:hover` (inline styles cannot express pseudo-classes). */
2074
- const cardHoverStyle = { borderColor: "var(--dsw-alias-label-dimmed)" };
2075
- /** Expanded, matching the built-in card's open state. */
2076
- const cardOpenStyle = {
2077
- background: "var(--dsw-alias-bg-layer-2)",
2078
- borderColor: "var(--dsw-alias-label-dimmed)"
2079
- };
2080
- const headerButtonStyle = {
2081
- boxSizing: "border-box",
2082
- width: "100%",
2083
- display: "flex",
2084
- alignItems: "center",
2085
- gap: 12,
2086
- border: 0,
2087
- borderRadius: 12,
2088
- padding: "14px 16px",
2089
- background: "transparent",
2090
- color: "inherit",
2091
- font: "inherit",
2092
- textAlign: "left",
2093
- cursor: "pointer",
2094
- appearance: "none"
2095
- };
2096
- const headerFocusStyle = {
2097
- outline: "2px solid var(--dsw-alias-brand-primary)",
2098
- outlineOffset: -2
2099
- };
2100
- const headTextStyle = {
2101
- display: "flex",
2102
- flex: 1,
2103
- minWidth: 0,
2104
- flexDirection: "column",
2105
- gap: 4
2106
- };
2107
- const titleStyle = {
2108
- fontSize: 15,
2109
- lineHeight: 1.4,
2110
- fontWeight: 600,
2111
- color: "var(--dsw-alias-label-primary)"
2112
- };
2113
- const introStyle = {
2114
- fontSize: 13,
2115
- lineHeight: 1.5,
2116
- color: "var(--dsw-alias-label-tertiary)"
2117
- };
2118
- /** The variant cards' chevron rule. */
2119
- const chevronStyle = {
2120
- flex: "none",
2121
- display: "flex",
2122
- color: "var(--dsw-alias-label-tertiary)",
2123
- transition: "transform .16s"
2124
- };
2125
- /** The variant cards' body: hairline top border, inset margins, no extra box. */
2126
- const cardBodyStyle = {
2127
- borderTop: ".5px solid var(--dsw-alias-border-l2)",
2128
- margin: "0 16px",
2129
- padding: "12px 0 8px"
2130
- };
2131
- /**
2132
- * One settings row: NO box of its own (the bordered rows read as nested
2133
- * cards, which the user ruled against) — rows are separated by a hairline
2134
- * bottom rule like the settings shell's own preference lists.
2135
- */
2136
- const rowStyle = {
2137
- display: "flex",
2138
- alignItems: "center",
2139
- gap: 12,
2140
- borderBottom: ".5px solid var(--dsw-alias-border-l2)",
2141
- paddingBottom: 10
2142
- };
2143
- const rowTextStyle = {
2144
- display: "flex",
2145
- flex: 1,
2146
- minWidth: 0,
2147
- flexDirection: "column",
2148
- gap: 2
2149
- };
2150
- const labelStyle = {
2151
- fontSize: 13,
2152
- fontWeight: 500,
2153
- lineHeight: 1.5,
2154
- color: "var(--dsw-alias-label-primary)"
2155
- };
2156
- const hintStyle = {
2157
- fontSize: 12,
2158
- lineHeight: 1.5,
2159
- color: "var(--dsw-alias-label-tertiary)"
2160
- };
2161
- const switchStyle = {
2162
- flex: "none",
2163
- display: "flex",
2164
- width: 36,
2165
- height: 20,
2166
- borderRadius: 10,
2167
- borderWidth: "1px",
2168
- borderStyle: "solid",
2169
- borderColor: "var(--dsw-alias-border-l2)",
2170
- padding: 1,
2171
- cursor: "pointer",
2172
- alignItems: "center",
2173
- transition: "background .16s"
2174
- };
2175
- const knobStyle = {
2176
- display: "block",
2177
- width: 16,
2178
- height: 16,
2179
- borderRadius: "50%",
2180
- background: "var(--dsw-alias-bg-layer-1, #fff)",
2181
- boxShadow: "0 1px 2px rgba(0,0,0,0.2)"
2182
- };
2183
- const pollFieldStyle = {
2184
- display: "flex",
2185
- alignItems: "center",
2186
- gap: 6,
2187
- flex: "none"
2188
- };
2189
- const inputStyle = {
2190
- boxSizing: "border-box",
2191
- width: 55,
2192
- padding: "5px 8px",
2193
- borderWidth: "1px",
2194
- borderStyle: "solid",
2195
- borderColor: "var(--dsw-alias-border-l2)",
2196
- borderRadius: 8,
2197
- background: "var(--dsw-alias-bg-layer-2)",
2198
- color: "var(--dsw-alias-label-primary)",
2199
- font: "inherit",
2200
- fontSize: 13,
2201
- textAlign: "right"
2202
- };
2203
2390
  //#endregion
2204
2391
  //#region src/client/quota-merge.ts
2205
2392
  /**
@@ -2441,6 +2628,7 @@ window.__ModuleLoader__.load({
2441
2628
  (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSettingsRevision);
2442
2629
  const enabled = variantId === "qoder" ? quotaToggles().cn : quotaToggles().global;
2443
2630
  const status = quotaStatus(variantId);
2631
+ const signedIn = status?.status === "signed-in";
2444
2632
  (0, react.useEffect)(() => {
2445
2633
  if (statusPath === void 0 || !enabled) return void 0;
2446
2634
  let disposed = false;
@@ -2464,21 +2652,22 @@ window.__ModuleLoader__.load({
2464
2652
  if (!disposed) setFailed(true);
2465
2653
  }
2466
2654
  };
2655
+ const isHidden = () => typeof document !== "undefined" && document.hidden;
2467
2656
  const loop = () => {
2468
- if (document.hidden) return;
2657
+ if (isHidden()) return;
2469
2658
  refresh();
2470
2659
  };
2471
2660
  timer = window.setInterval(loop, Math.max(6e4, quotaPollMs()));
2472
2661
  refresh();
2473
2662
  const onVisible = () => {
2474
- if (!document.hidden) loop();
2663
+ if (!isHidden()) loop();
2475
2664
  };
2476
- document.addEventListener("visibilitychange", onVisible);
2665
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisible);
2477
2666
  return () => {
2478
2667
  disposed = true;
2479
2668
  controller.abort();
2480
2669
  if (timer !== void 0) window.clearInterval(timer);
2481
- document.removeEventListener("visibilitychange", onVisible);
2670
+ if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisible);
2482
2671
  };
2483
2672
  }, [
2484
2673
  statusPath,
@@ -2507,7 +2696,11 @@ window.__ModuleLoader__.load({
2507
2696
  className: "qdp-railButton",
2508
2697
  "aria-label": title,
2509
2698
  title,
2510
- onClick: () => open?.(),
2699
+ disabled: !signedIn,
2700
+ onClick: () => {
2701
+ if (!signedIn) return;
2702
+ open?.();
2703
+ },
2511
2704
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Ring, {
2512
2705
  percent: ringPercent,
2513
2706
  warn: ringWarn,
@@ -2519,7 +2712,11 @@ window.__ModuleLoader__.load({
2519
2712
  className: "qdp-foot",
2520
2713
  "aria-label": title,
2521
2714
  title,
2522
- onClick: () => open?.(),
2715
+ disabled: !signedIn,
2716
+ onClick: () => {
2717
+ if (!signedIn) return;
2718
+ open?.();
2719
+ },
2523
2720
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2524
2721
  className: "qdp-footTop",
2525
2722
  children: [
@@ -2983,6 +3180,10 @@ window.__ModuleLoader__.load({
2983
3180
  intro: "Use the models from your Qoder (China) account directly in DSH — paste a Personal Access Token, no browser sign-in flow.",
2984
3181
  titleAI: "Qoder Global",
2985
3182
  introAI: "Use the models from your Qoder Global account directly in DSH — paste a Personal Access Token, no browser sign-in flow.",
3183
+ unifiedTitle: "Qoder",
3184
+ unifiedIntro: "Manage Qoder (China) and Qoder Global models, credentials, and sidebar quota displays.",
3185
+ variantTabCN: "China",
3186
+ variantTabGlobal: "Global",
2986
3187
  expand: "Expand",
2987
3188
  collapse: "Collapse",
2988
3189
  loading: "Loading account…",
@@ -3012,6 +3213,17 @@ window.__ModuleLoader__.load({
3012
3213
  tabStatus: "Status",
3013
3214
  tabContext: "Context window",
3014
3215
  tabDetails: "Credit details",
3216
+ tabCheckIn: "Check-in log",
3217
+ checkInLogTime: "Check-in time",
3218
+ checkInLogResult: "Result",
3219
+ checkInLogAmount: "Credits",
3220
+ checkInLogEmpty: "No check-in logs recorded yet.",
3221
+ checkInNow: "Check in now",
3222
+ checkInChecking: "Checking in…",
3223
+ checkInRefresh: "Refresh",
3224
+ checkInRefreshing: "Refreshing…",
3225
+ checkInClear: "Clear logs",
3226
+ checkInClearing: "Clearing…",
3015
3227
  creditsDetailHeading: "By package",
3016
3228
  creditsUsed: "Used this cycle: {percent}%",
3017
3229
  creditsTotalUnlimited: "Total: Unlimited",
@@ -3074,6 +3286,14 @@ window.__ModuleLoader__.load({
3074
3286
  quotaToggleCN: "Show China credit card",
3075
3287
  quotaToggleGlobal: "Show global credit card",
3076
3288
  quotaToggleHint: "Show this account’s remaining credit in the sidebar footer.",
3289
+ autoCheckInCN: "Qoder (China) daily auto check-in (100 Credits)",
3290
+ autoCheckInGlobal: "Qoder Global (International) daily auto check-in",
3291
+ autoCheckInHintCN: "Automatically check in daily at 10:00 (UTC+8) to claim 100 Credits for China account.",
3292
+ autoCheckInHintGlobal: "Automatically check in daily at 10:00 (UTC+8) to claim available perks for Global account.",
3293
+ autoCheckInStatusClaimed: "Checked in today (+{amount} Credits)",
3294
+ autoCheckInStatusAlready: "Already checked in today",
3295
+ autoCheckInStatusNoCampaign: "No active benefit campaign today",
3296
+ autoCheckInStatusError: "Auto check-in error: {message}",
3077
3297
  quotaSignInRequired: "Save a PAT for this variant first to enable this card.",
3078
3298
  quotaPollLabel: "Refresh interval",
3079
3299
  quotaPollHint: "Applies to both quota cards. Longer is kinder to the billing endpoint.",
@@ -3113,6 +3333,10 @@ window.__ModuleLoader__.load({
3113
3333
  intro: "保存个人访问令牌(PAT)后,即可在 DSH 中直接使用 Qoder 国内版的模型。",
3114
3334
  titleAI: "Qoder Global(国际版)",
3115
3335
  introAI: "保存个人访问令牌(PAT)后,即可在 DSH 中直接使用 Qoder Global 的模型。",
3336
+ unifiedTitle: "Qoder",
3337
+ unifiedIntro: "统一管理 Qoder(国内版)与 Qoder Global(国际版)模型、凭证及侧栏额度展示。",
3338
+ variantTabCN: "国内版",
3339
+ variantTabGlobal: "国际版",
3116
3340
  expand: "展开",
3117
3341
  collapse: "收起",
3118
3342
  loading: "正在读取账号…",
@@ -3142,6 +3366,17 @@ window.__ModuleLoader__.load({
3142
3366
  tabStatus: "状态",
3143
3367
  tabContext: "上下文窗口",
3144
3368
  tabDetails: "额度明细",
3369
+ tabCheckIn: "签到日志",
3370
+ checkInLogTime: "签到时间",
3371
+ checkInLogResult: "签到结果",
3372
+ checkInLogAmount: "获得额度",
3373
+ checkInLogEmpty: "暂无签到日志记录。",
3374
+ checkInNow: "立即签到",
3375
+ checkInChecking: "正在签到…",
3376
+ checkInRefresh: "刷新",
3377
+ checkInRefreshing: "正在刷新…",
3378
+ checkInClear: "清空日志",
3379
+ checkInClearing: "正在清空…",
3145
3380
  creditsDetailHeading: "按套餐",
3146
3381
  creditsUsed: "本轮已用 {percent}%",
3147
3382
  creditsTotalUnlimited: "合计:不限额",
@@ -3204,6 +3439,14 @@ window.__ModuleLoader__.load({
3204
3439
  quotaToggleCN: "展示国内版额度",
3205
3440
  quotaToggleGlobal: "展示国际版额度",
3206
3441
  quotaToggleHint: "在侧栏底部展示该账号的剩余额度。",
3442
+ autoCheckInCN: "Qoder(国内版)每日自动签到",
3443
+ autoCheckInGlobal: "Qoder Global(国际版)每日自动签到",
3444
+ autoCheckInHintCN: "每天 10:00 (UTC+8) 自动签到领取 100 Credits 算力额度。",
3445
+ autoCheckInHintGlobal: "每天 10:00 (UTC+8) 自动检测并领取国际版可用福利额度。",
3446
+ autoCheckInStatusClaimed: "今日已自动签到(+{amount} Credits)",
3447
+ autoCheckInStatusAlready: "今日已完成签到",
3448
+ autoCheckInStatusNoCampaign: "今日无可用签到福利活动",
3449
+ autoCheckInStatusError: "自动签到出错:{message}",
3207
3450
  quotaSignInRequired: "请先为该变体保存 PAT,再开启此开关。",
3208
3451
  quotaPollLabel: "刷新间隔",
3209
3452
  quotaPollHint: "对两张额度卡片同时生效。间隔越长对计费接口越友好。",
@@ -3299,10 +3542,6 @@ window.__ModuleLoader__.load({
3299
3542
  en
3300
3543
  }), "dsh-qoder-connect: settings copy");
3301
3544
  const t = ctx.locale.bind(namespace);
3302
- const quotaSettingsInjected = {
3303
- t,
3304
- signedIn: () => quotaSignInState()
3305
- };
3306
3545
  let quotaScope;
3307
3546
  try {
3308
3547
  const scope = ctx.settingsScope.bind({ namespace: "qoder-quota" });
@@ -3319,20 +3558,13 @@ window.__ModuleLoader__.load({
3319
3558
  }
3320
3559
  ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
3321
3560
  name: "settings.plugin.item",
3322
- key: "qoder-quota",
3561
+ key: "qoder",
3323
3562
  priority: 50,
3324
- inject: () => ({
3325
- ...quotaSettingsInjected,
3326
- scope: quotaScope
3327
- })
3328
- }, QuotaSettingsCard));
3329
- for (const [index, variant] of QODER_CARD_VARIANTS.entries()) ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
3330
- name: "settings.plugin.item",
3331
- key: variant.id,
3332
- priority: 60 + index * 10,
3333
3563
  inject: () => ({
3334
3564
  t,
3335
- variant
3565
+ scope: quotaScope,
3566
+ signedIn: () => quotaSignInState(),
3567
+ unified: true
3336
3568
  })
3337
3569
  }, QoderPluginCard));
3338
3570
  const QUOTA_PANEL_ID = "qoder-quota-panel";