@iasiv5/dsh-quota-watch 0.1.18 → 0.1.19
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/README.md +1 -1
- package/lib/client.js +23 -6
- package/package.json +1 -1
- package/src/client.mjs +39 -3
- package/src/host.mjs +15 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- **🧩 侧边栏卡片(可选,默认隐藏)**:胶囊是默认界面;需要侧栏卡片时从面板底部按钮开启(Web profile 侧栏底部区块存在时才出现该按钮)。卡片提供摘要行 + 行点击开面板 + 行首 provider 名称点击立即探测。侧栏收起(Web 窄栏)或容器过窄(<160px,跨 profile 通用判据)时自动隐藏,侧栏整体不存在(Desktop 收起态)同样自然缺席、无报错——入口始终由悬浮胶囊承载。
|
|
24
24
|
- **🌏 语言跟随宿主**:界面文案跟随 DSH「设置 → 语言」——宿主 locale 特性把解析结果实时镜像到 `<html lang>`,插件监听该属性,切换即热重渲染(无需刷新页面);宿主未同步时回退浏览器语言。
|
|
25
25
|
- **🛟 降级与容错**:凭据缺失的 provider 不渲染摘要行与胶囊分段(两者皆缺时悬浮胶囊与卡片一并隐藏);探测失败时保留最后一次成功数据并在行上加 ⚠ 过期标记(⚠ 只出现在面板与卡片行内,不在胶囊上显示)。
|
|
26
|
-
- **🔁 刷新节奏**:宿主默认每 60 秒探测(窗口 + 用量 + Copilot),profile 可配 30–3600 秒;浏览器页签可见时每 30
|
|
26
|
+
- **🔁 刷新节奏**:宿主默认每 60 秒探测(窗口 + 用量 + Copilot),profile 可配 30–3600 秒;浏览器页签可见时每 30 秒刷新视图,隐藏时暂停。重启冷启动零等待:宿主 0 秒即发首轮探测(网络未就绪时以 5 秒冷重试直至拿到数据),客户端在无数据期间以 2 秒起步的指数退避快速重试、拿到数据后回到常规节奏——重启后数秒内胶囊即出现,不再有 ~30 秒空窗。所有 UI 收在 Shadow DOM 里(ADR 0001),颜色走 DSH 主题语义变量(含硬编码兜底),自动适配浅色 / 深色主题。
|
|
27
27
|
|
|
28
28
|
## 📸 界面巡礼
|
|
29
29
|
|
package/lib/client.js
CHANGED
|
@@ -470,7 +470,9 @@ function mountQuotaCard({
|
|
|
470
470
|
doc = globalThis.document,
|
|
471
471
|
win = globalThis.window,
|
|
472
472
|
fetchImpl = globalThis.fetch,
|
|
473
|
-
pollIntervalMs = CLIENT_POLL_INTERVAL_MS
|
|
473
|
+
pollIntervalMs = CLIENT_POLL_INTERVAL_MS,
|
|
474
|
+
retryNoDataMs = 2e3,
|
|
475
|
+
retryBackoff = 2
|
|
474
476
|
} = {}) {
|
|
475
477
|
if (!doc?.body || typeof fetchImpl !== "function") return () => {
|
|
476
478
|
};
|
|
@@ -670,11 +672,12 @@ function mountQuotaCard({
|
|
|
670
672
|
let snapshot;
|
|
671
673
|
let requestSequence = 0;
|
|
672
674
|
let timer;
|
|
675
|
+
let noDataRetries = 0;
|
|
673
676
|
let disposed = false;
|
|
674
677
|
let queuedPlace = false;
|
|
675
678
|
let openKey;
|
|
676
679
|
let panelNav = false;
|
|
677
|
-
let dataHidden =
|
|
680
|
+
let dataHidden = true;
|
|
678
681
|
let surfaceFlags = loadSurfaceFlags(win.localStorage);
|
|
679
682
|
const setCardHidden = (hidden) => {
|
|
680
683
|
surfaceFlags = { cardHidden: hidden };
|
|
@@ -1059,6 +1062,13 @@ function mountQuotaCard({
|
|
|
1059
1062
|
if (sequence === requestSequence && !disposed) render(next);
|
|
1060
1063
|
} catch {
|
|
1061
1064
|
if (sequence === requestSequence && !disposed) showTransportError();
|
|
1065
|
+
} finally {
|
|
1066
|
+
if (timer === void 0 && sequence === requestSequence && !disposed && doc.visibilityState !== "hidden") {
|
|
1067
|
+
timer = win.setTimeout(() => {
|
|
1068
|
+
timer = void 0;
|
|
1069
|
+
if (!disposed && doc.visibilityState !== "hidden") void poll();
|
|
1070
|
+
}, nextPollDelay());
|
|
1071
|
+
}
|
|
1062
1072
|
}
|
|
1063
1073
|
};
|
|
1064
1074
|
const place = () => {
|
|
@@ -1089,16 +1099,23 @@ function mountQuotaCard({
|
|
|
1089
1099
|
if (!disposed) renderAll();
|
|
1090
1100
|
}) : null;
|
|
1091
1101
|
langObserver?.observe(doc.documentElement, { attributeFilter: ["lang"] });
|
|
1102
|
+
syncFloatVisibility();
|
|
1092
1103
|
place();
|
|
1104
|
+
const nextPollDelay = () => {
|
|
1105
|
+
if (!dataHidden) {
|
|
1106
|
+
noDataRetries = 0;
|
|
1107
|
+
return pollIntervalMs;
|
|
1108
|
+
}
|
|
1109
|
+
const delay = retryNoDataMs * retryBackoff ** noDataRetries;
|
|
1110
|
+
noDataRetries += 1;
|
|
1111
|
+
return Math.min(delay, pollIntervalMs);
|
|
1112
|
+
};
|
|
1093
1113
|
const startPolling = () => {
|
|
1094
1114
|
if (timer !== void 0 || doc.visibilityState === "hidden" || disposed) return;
|
|
1095
1115
|
void poll();
|
|
1096
|
-
timer = win.setInterval(() => {
|
|
1097
|
-
void poll();
|
|
1098
|
-
}, pollIntervalMs);
|
|
1099
1116
|
};
|
|
1100
1117
|
const stopPolling = () => {
|
|
1101
|
-
if (timer !== void 0) win.
|
|
1118
|
+
if (timer !== void 0) win.clearTimeout(timer);
|
|
1102
1119
|
timer = void 0;
|
|
1103
1120
|
};
|
|
1104
1121
|
const onVisibilityChange = () => {
|
package/package.json
CHANGED
package/src/client.mjs
CHANGED
|
@@ -373,6 +373,8 @@ export function mountQuotaCard({
|
|
|
373
373
|
win = globalThis.window,
|
|
374
374
|
fetchImpl = globalThis.fetch,
|
|
375
375
|
pollIntervalMs = CLIENT_POLL_INTERVAL_MS,
|
|
376
|
+
retryNoDataMs = 2_000,
|
|
377
|
+
retryBackoff = 2,
|
|
376
378
|
} = {}) {
|
|
377
379
|
if (!doc?.body || typeof fetchImpl !== 'function') return () => {}
|
|
378
380
|
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {}
|
|
@@ -610,11 +612,15 @@ export function mountQuotaCard({
|
|
|
610
612
|
let snapshot
|
|
611
613
|
let requestSequence = 0
|
|
612
614
|
let timer
|
|
615
|
+
let noDataRetries = 0
|
|
613
616
|
let disposed = false
|
|
614
617
|
let queuedPlace = false
|
|
615
618
|
let openKey
|
|
616
619
|
let panelNav = false
|
|
617
|
-
|
|
620
|
+
// Cold until the first successful render: a mount whose first poll fails or
|
|
621
|
+
// stalls must behave like the documented no-data state (hidden shell, fast
|
|
622
|
+
// retries), not like a warm client showing an empty capsule.
|
|
623
|
+
let dataHidden = true
|
|
618
624
|
let surfaceFlags = loadSurfaceFlags(win.localStorage)
|
|
619
625
|
const setCardHidden = (hidden) => {
|
|
620
626
|
surfaceFlags = { cardHidden: hidden }
|
|
@@ -1052,6 +1058,18 @@ export function mountQuotaCard({
|
|
|
1052
1058
|
if (sequence === requestSequence && !disposed) render(next)
|
|
1053
1059
|
} catch {
|
|
1054
1060
|
if (sequence === requestSequence && !disposed) showTransportError()
|
|
1061
|
+
} finally {
|
|
1062
|
+
// Self-scheduling chain (replaces the fixed interval): every completed
|
|
1063
|
+
// poll — success OR transport error — arms the next one, so the no-data
|
|
1064
|
+
// backoff also covers a cold start whose first request fails outright.
|
|
1065
|
+
// timer === undefined guards against a concurrent force poll forking a
|
|
1066
|
+
// second chain; the already-armed timer keeps the cadence.
|
|
1067
|
+
if (timer === undefined && sequence === requestSequence && !disposed && doc.visibilityState !== 'hidden') {
|
|
1068
|
+
timer = win.setTimeout(() => {
|
|
1069
|
+
timer = undefined
|
|
1070
|
+
if (!disposed && doc.visibilityState !== 'hidden') void poll()
|
|
1071
|
+
}, nextPollDelay())
|
|
1072
|
+
}
|
|
1055
1073
|
}
|
|
1056
1074
|
}
|
|
1057
1075
|
|
|
@@ -1091,15 +1109,33 @@ export function mountQuotaCard({
|
|
|
1091
1109
|
? new win.MutationObserver(() => { if (!disposed) renderAll() })
|
|
1092
1110
|
: null
|
|
1093
1111
|
langObserver?.observe(doc.documentElement, { attributeFilter: ['lang'] })
|
|
1112
|
+
// Cold until data lands: keep the shell hidden so a slow or failed first
|
|
1113
|
+
// probe never flashes an empty capsule (startPolling arms the retry chain).
|
|
1114
|
+
syncFloatVisibility()
|
|
1094
1115
|
place()
|
|
1095
1116
|
|
|
1117
|
+
/** Cold-start cadence: while no provider has rendered any data, re-poll on
|
|
1118
|
+
* a shortening backoff (retryNoDataMs × retryBackoffⁿ, capped at
|
|
1119
|
+
* pollIntervalMs) so a desktop cold start surfaces the capsule in seconds
|
|
1120
|
+
* instead of waiting out the fixed interval. A successful render resets the
|
|
1121
|
+
* chain to the normal cadence; a transport error keeps the previous
|
|
1122
|
+
* dataHidden state, so a cold client keeps retrying fast and a warm one
|
|
1123
|
+
* stays calm. */
|
|
1124
|
+
const nextPollDelay = () => {
|
|
1125
|
+
if (!dataHidden) {
|
|
1126
|
+
noDataRetries = 0
|
|
1127
|
+
return pollIntervalMs
|
|
1128
|
+
}
|
|
1129
|
+
const delay = retryNoDataMs * (retryBackoff ** noDataRetries)
|
|
1130
|
+
noDataRetries += 1
|
|
1131
|
+
return Math.min(delay, pollIntervalMs)
|
|
1132
|
+
}
|
|
1096
1133
|
const startPolling = () => {
|
|
1097
1134
|
if (timer !== undefined || doc.visibilityState === 'hidden' || disposed) return
|
|
1098
1135
|
void poll()
|
|
1099
|
-
timer = win.setInterval(() => { void poll() }, pollIntervalMs)
|
|
1100
1136
|
}
|
|
1101
1137
|
const stopPolling = () => {
|
|
1102
|
-
if (timer !== undefined) win.
|
|
1138
|
+
if (timer !== undefined) win.clearTimeout(timer)
|
|
1103
1139
|
timer = undefined
|
|
1104
1140
|
}
|
|
1105
1141
|
const onVisibilityChange = () => {
|
package/src/host.mjs
CHANGED
|
@@ -311,7 +311,18 @@ export function createQuotaService(ctx, options = {}) {
|
|
|
311
311
|
timer = setTimeout(async () => {
|
|
312
312
|
timer = undefined
|
|
313
313
|
await refresh()
|
|
314
|
-
|
|
314
|
+
// Cold-start retry: until at least one provider has reached ready/stale,
|
|
315
|
+
// a failed or still-loading round re-arms shortly instead of waiting a
|
|
316
|
+
// full interval — a desktop cold start with a slow network would
|
|
317
|
+
// otherwise stare at no capsule for up to a minute. (updatedAt alone
|
|
318
|
+
// cannot signal "cold": refresh() stamps it even when every probe
|
|
319
|
+
// failed.) All-'missing' (no credentials configured) and warm states
|
|
320
|
+
// keep the normal cadence, so nobody without credentials gets
|
|
321
|
+
// high-frequency probing.
|
|
322
|
+
const states = [...providers.values()].map((provider) => provider.status)
|
|
323
|
+
const retryable = states.some((status) => status === 'loading' || status === 'error')
|
|
324
|
+
&& states.every((status) => status !== 'ready' && status !== 'stale')
|
|
325
|
+
schedule(retryable ? (options.coldRetryMs ?? 5_000) : pollIntervalSec * 1000)
|
|
315
326
|
}, delayMs)
|
|
316
327
|
timer.unref?.()
|
|
317
328
|
}
|
|
@@ -321,7 +332,9 @@ export function createQuotaService(ctx, options = {}) {
|
|
|
321
332
|
refresh,
|
|
322
333
|
start() {
|
|
323
334
|
if (disposed || timer !== undefined) return
|
|
324
|
-
|
|
335
|
+
// Routes register synchronously in apply() before start(), so probing
|
|
336
|
+
// immediately is safe; the legacy 2s delay only padded the cold start.
|
|
337
|
+
schedule(0)
|
|
325
338
|
},
|
|
326
339
|
stop() {
|
|
327
340
|
disposed = true
|