quotacap 0.0.28 → 0.0.30
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/dist/adapters/codex.js +16 -4
- package/dist/advisory/engine.d.ts +3 -1
- package/dist/advisory/engine.js +32 -8
- package/dist/advisory/snapshot.js +1 -0
- package/dist/advisory/types.d.ts +14 -0
- package/dist/advisory/types.js +7 -1
- package/dist/cli/runtime.d.ts +1 -0
- package/dist/cli/runtime.js +8 -1
- package/dist/diagnostics/failure.js +45 -0
- package/dist/format/markdown.js +3 -3
- package/dist/format/rows.d.ts +4 -1
- package/dist/format/rows.js +25 -1
- package/dist/format/terminal.js +10 -3
- package/dist/mcp/server.js +1 -1
- package/dist/store/quotas.js +2 -1
- package/dist/version.js +1 -1
- package/dist/web/src/components/AdviceDrawer.js +2 -1
- package/dist/web/src/components/FaultBanner.js +6 -0
- package/dist/web/src/components/PaceBar.d.ts +53 -3
- package/dist/web/src/components/PaceBar.js +76 -11
- package/dist/web/src/components/PacingLegend.js +3 -2
- package/dist/web/src/components/ProviderCard.js +3 -2
- package/dist/web/src/components/ProviderDrawer.js +16 -4
- package/dist/web/src/components/ProviderRow.js +3 -3
- package/dist/web/src/components/ResetRail.js +1 -1
- package/dist/web/src/components/SubscriptionList.js +1 -1
- package/dist/web/src/state.d.ts +7 -0
- package/dist/webAssets.js +1 -1
- package/dist/webHtml.js +1 -1
- package/package.json +1 -1
- package/web/dist/assets/{index-TJk_KDbS.css → index-BRBtLIOV.css} +1 -1
- package/web/dist/assets/{index-hmABfkqB.js → index-DnWV59MD.js} +2 -2
- package/web/dist/index.html +2 -2
|
@@ -12,6 +12,8 @@ export function paceBadge(provider) {
|
|
|
12
12
|
return "Not reporting";
|
|
13
13
|
if (advisory.status === "at risk")
|
|
14
14
|
return "Cap risk";
|
|
15
|
+
if (advisory.status === "unknown")
|
|
16
|
+
return "Measuring";
|
|
15
17
|
switch (advisory.urgency) {
|
|
16
18
|
case "burn now":
|
|
17
19
|
case "use soon":
|
|
@@ -23,6 +25,46 @@ export function paceBadge(provider) {
|
|
|
23
25
|
return "On track";
|
|
24
26
|
}
|
|
25
27
|
}
|
|
28
|
+
/** Split the advisory into its two displayed paces. `burnRate` doubles as the
|
|
29
|
+
* forecast input, so the recent figure only exists when it was measured. */
|
|
30
|
+
export function paceFigures(advisory) {
|
|
31
|
+
if (!advisory)
|
|
32
|
+
return { avg: null, recent: null };
|
|
33
|
+
const avg = advisory.avgPace ?? null;
|
|
34
|
+
const recent = advisory.paceSource === "recent" ? advisory.burnRate : null;
|
|
35
|
+
return { avg, recent };
|
|
36
|
+
}
|
|
37
|
+
/** Card pace cells: average and 24h rate side by side, each "—" when unknown.
|
|
38
|
+
* Unlike the collapsed summaries, both cells always render so the pair
|
|
39
|
+
* reads as two peer figures. */
|
|
40
|
+
export function paceCells(advisory) {
|
|
41
|
+
const { avg, recent } = paceFigures(advisory);
|
|
42
|
+
return {
|
|
43
|
+
avg: avg !== null ? `${avg.toFixed(1)}%/day` : "—",
|
|
44
|
+
recent: recent !== null ? `${recent.toFixed(1)}%/day` : "—",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Hover explanations shared by cards and rows. Plain words, no jargon. */
|
|
48
|
+
export const PACE_TIPS = {
|
|
49
|
+
avg: "Window average — quota used ÷ days elapsed. Your overall pace this window.",
|
|
50
|
+
recent: "Burn over the last 24 hours. Forecasts use this rate when recent readings exist.",
|
|
51
|
+
ideal: "The even pace that lands exactly on 100% at reset: remaining ÷ days left.",
|
|
52
|
+
};
|
|
53
|
+
/** Table-row pace stack: one labeled line per known pace plus the needed
|
|
54
|
+
* rate. Both paces always show when known (no collapsing) so rows read
|
|
55
|
+
* uniformly. Empty when no advisory is present. */
|
|
56
|
+
export function paceLines(advisory) {
|
|
57
|
+
if (!advisory)
|
|
58
|
+
return [];
|
|
59
|
+
const { avg, recent } = paceFigures(advisory);
|
|
60
|
+
const lines = [];
|
|
61
|
+
if (avg !== null)
|
|
62
|
+
lines.push({ label: "Avg", value: `${avg.toFixed(1)}%/day`, tip: PACE_TIPS.avg });
|
|
63
|
+
if (recent !== null)
|
|
64
|
+
lines.push({ label: "24h", value: `${recent.toFixed(1)}%/day`, tip: PACE_TIPS.recent });
|
|
65
|
+
lines.push({ label: "Ideal", value: `${advisory.idealRate.toFixed(1)}%/day`, tip: PACE_TIPS.ideal });
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
26
68
|
/** Plain-words reason shown beside "Not reporting". Null when the badge says it all. */
|
|
27
69
|
export function exclusionDetail(reason) {
|
|
28
70
|
switch (reason) {
|
|
@@ -39,16 +81,6 @@ export function exclusionDetail(reason) {
|
|
|
39
81
|
return null;
|
|
40
82
|
}
|
|
41
83
|
}
|
|
42
|
-
const EVIDENCE_LABELS = {
|
|
43
|
-
measured: "Measured",
|
|
44
|
-
"window-average": "Window avg",
|
|
45
|
-
};
|
|
46
|
-
/** Coordinator-locked evidence labels. Unknown tokens pass through verbatim. */
|
|
47
|
-
export function evidenceLabels(evidence) {
|
|
48
|
-
return evidence
|
|
49
|
-
.filter((e) => e !== "estimated-reset")
|
|
50
|
-
.map((e) => EVIDENCE_LABELS[e] ?? e);
|
|
51
|
-
}
|
|
52
84
|
export function isEstimated(provider) {
|
|
53
85
|
return provider.evidence.includes("estimated-reset");
|
|
54
86
|
}
|
|
@@ -103,6 +135,30 @@ export function resetCountdown(provider, asOfMs) {
|
|
|
103
135
|
const estimated = isEstimated(provider) ? " (est.)" : "";
|
|
104
136
|
return `in ${left}${estimated}`;
|
|
105
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Reset cell for the table view: clock time plus countdown, e.g.
|
|
140
|
+
* "Tue 10:25 · in 20h 30m". Estimated resets mark the timestamp, per the
|
|
141
|
+
* 0.0.25 convention. Falls back to the countdown-only wording for
|
|
142
|
+
* reset-passed, missing, and unparseable rows.
|
|
143
|
+
*/
|
|
144
|
+
export function resetCell(provider, asOfMs) {
|
|
145
|
+
if (provider.exclusionReason === "reset-passed")
|
|
146
|
+
return "awaiting fresh window";
|
|
147
|
+
if (!provider.quota)
|
|
148
|
+
return "no readings";
|
|
149
|
+
const clock = resetClock(provider.quota.resetsAt);
|
|
150
|
+
const left = timeLeft(provider.quota.resetsAt, asOfMs);
|
|
151
|
+
const estimated = isEstimated(provider) ? " (est.)" : "";
|
|
152
|
+
if (clock && left)
|
|
153
|
+
return `${clock}${estimated} · in ${left}`;
|
|
154
|
+
if (left === null) {
|
|
155
|
+
const resetMs = Date.parse(provider.quota.resetsAt);
|
|
156
|
+
if (Number.isFinite(resetMs) && resetMs <= asOfMs)
|
|
157
|
+
return "awaiting fresh window";
|
|
158
|
+
return "reset unknown";
|
|
159
|
+
}
|
|
160
|
+
return "reset unknown";
|
|
161
|
+
}
|
|
106
162
|
/** Bar geometry from server timestamps and percents. Presentation only. */
|
|
107
163
|
export function barGeometry(provider, asOfMs) {
|
|
108
164
|
const usedPct = provider.quota ? Math.min(100, Math.max(0, provider.quota.usedPct)) : 0;
|
|
@@ -126,6 +182,8 @@ export function badgeColor(badge) {
|
|
|
126
182
|
return "var(--ahead)";
|
|
127
183
|
case "Cap risk":
|
|
128
184
|
return "var(--danger)";
|
|
185
|
+
case "Measuring":
|
|
186
|
+
return "var(--ink-soft)";
|
|
129
187
|
case "Not reporting":
|
|
130
188
|
return "var(--line-strong)";
|
|
131
189
|
}
|
|
@@ -140,6 +198,8 @@ export function fillToken(badge) {
|
|
|
140
198
|
return "var(--fill-ahead)";
|
|
141
199
|
case "Cap risk":
|
|
142
200
|
return "var(--fill-cap)";
|
|
201
|
+
case "Measuring":
|
|
202
|
+
return "var(--fill-out)";
|
|
143
203
|
case "Not reporting":
|
|
144
204
|
return "var(--fill-out)";
|
|
145
205
|
}
|
|
@@ -154,6 +214,8 @@ export function edgeToken(badge) {
|
|
|
154
214
|
return "var(--edge-ahead)";
|
|
155
215
|
case "Cap risk":
|
|
156
216
|
return "var(--edge-cap)";
|
|
217
|
+
case "Measuring":
|
|
218
|
+
return "var(--edge-out)";
|
|
157
219
|
case "Not reporting":
|
|
158
220
|
return "var(--edge-out)";
|
|
159
221
|
}
|
|
@@ -188,7 +250,7 @@ export function Badge({ provider }) {
|
|
|
188
250
|
? "pace-ahead"
|
|
189
251
|
: badge === "Cap risk"
|
|
190
252
|
? "pace-cap"
|
|
191
|
-
: "pace-out";
|
|
253
|
+
: "pace-out"; // Not reporting and Measuring share the neutral style
|
|
192
254
|
return (_jsxs("span", { "data-testid": "pace-badge", className: `pace ${badgeClass}`, children: [badge, detail ? ` · ${detail}` : ""] }));
|
|
193
255
|
}
|
|
194
256
|
/**
|
|
@@ -236,6 +298,9 @@ export function forecastLine(provider) {
|
|
|
236
298
|
const advisory = provider.advisory;
|
|
237
299
|
if (!advisory)
|
|
238
300
|
return null;
|
|
301
|
+
if (advisory.remaining <= 0) {
|
|
302
|
+
return "Exhausted · quota fully used";
|
|
303
|
+
}
|
|
239
304
|
if (advisory.paceSource === "unknown") {
|
|
240
305
|
return `Measuring pace · ${Math.round(advisory.remaining)}% remaining`;
|
|
241
306
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
const STATES = [
|
|
4
|
-
{ label: "On track", cls: "pace-ontrack", meaning: "
|
|
4
|
+
{ label: "On track", cls: "pace-ontrack", meaning: "Burning at a rate the window sustains" },
|
|
5
5
|
{ label: "Behind pace", cls: "pace-behind", meaning: "Burning slow; risk of unused quota waste" },
|
|
6
6
|
{ label: "Ahead of pace", cls: "pace-ahead", meaning: "Burning faster than time; monitor usage" },
|
|
7
7
|
{ label: "Cap risk", cls: "pace-cap", meaning: "Will hit cap before reset window closes" },
|
|
8
|
+
{ label: "Measuring", cls: "pace-out", meaning: "Not enough data yet; pace appears after 6h" },
|
|
8
9
|
{ label: "Not reporting", cls: "pace-out", meaning: "No usable reading; excluded from ranking" },
|
|
9
10
|
];
|
|
10
11
|
export function PacingLegend() {
|
|
@@ -28,7 +29,7 @@ export function PacingLegend() {
|
|
|
28
29
|
document.removeEventListener("keydown", onKey);
|
|
29
30
|
};
|
|
30
31
|
}, [open]);
|
|
31
|
-
return (_jsxs("div", { className: "legend-wrap", ref: rootRef, children: [_jsx("button", { type: "button", className: "legend-btn", "aria-expanded": open, "aria-controls": "pacing-legend", onClick: () => setOpen((v) => !v), children: "Pacing Legend \u25BE" }), open && (_jsxs("div", { id: "pacing-legend", className: "legend-popover", role: "dialog", "aria-label": "Pacing legend", children: [_jsxs("div", { className: "legend-head", children: [_jsx("b", { children: "Pacing Scale Legend" }), _jsx("button", { type: "button", className: "legend-close", "aria-label": "Close pacing legend", onClick: () => setOpen(false), children: "\u00D7" })] }), _jsx("p", { className: "legend-intro", children: "Pacing measures quota burn against elapsed window time:" }), _jsx("div", { className: "legend-rows", children: STATES.map((s) => (_jsxs("div", { className: "legend-row", children: [_jsx("span", { className: `pace ${s.cls}`, children: s.label }), _jsx("span", { children: s.meaning })] }, s.label))) }), _jsxs("div", { className: "legend-foot", children: [_jsx("code", { children: "\u2588" }), " allowance used \u00B7 ", _jsx("code", { children: "\u2502" }), " time elapsed \u00B7 ", _jsx("code", { children: "\u2591" }), " projected waste"] })] }))] }));
|
|
32
|
+
return (_jsxs("div", { className: "legend-wrap", ref: rootRef, children: [_jsx("button", { type: "button", className: "legend-btn", "aria-expanded": open, "aria-controls": "pacing-legend", onClick: () => setOpen((v) => !v), children: "Pacing Legend \u25BE" }), open && (_jsxs("div", { id: "pacing-legend", className: "legend-popover", role: "dialog", "aria-label": "Pacing legend", children: [_jsxs("div", { className: "legend-head", children: [_jsx("b", { children: "Pacing Scale Legend" }), _jsx("button", { type: "button", className: "legend-close", "aria-label": "Close pacing legend", onClick: () => setOpen(false), children: "\u00D7" })] }), _jsx("p", { className: "legend-intro", children: "Pacing measures quota burn against elapsed window time:" }), _jsx("div", { className: "legend-rows", children: STATES.map((s) => (_jsxs("div", { className: "legend-row", children: [_jsx("span", { className: `pace ${s.cls}`, children: s.label }), _jsx("span", { children: s.meaning })] }, s.label))) }), _jsxs("div", { className: "legend-foot", children: [_jsx("code", { children: "\u2588" }), " allowance used \u00B7 ", _jsx("code", { children: "\u2502" }), " time elapsed \u00B7 ", _jsx("code", { children: "\u2591" }), " projected waste"] }), _jsx("div", { className: "legend-foot", children: "Avg = used \u00F7 days elapsed \u00B7 24h = burn over the last day \u00B7 Ideal = remaining \u00F7 days left" })] }))] }));
|
|
32
33
|
}
|
|
33
34
|
function servingHost() {
|
|
34
35
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ageDuration } from "../state.js";
|
|
3
|
-
import { Badge, PaceBar, isEstimated, resetClock, resetCountdown, timeLeft, } from "./PaceBar.js";
|
|
3
|
+
import { Badge, PACE_TIPS, PaceBar, isEstimated, paceCells, resetClock, resetCountdown, timeLeft, } from "./PaceBar.js";
|
|
4
4
|
import { ProviderIcon, providerTint } from "./ProviderIcon.js";
|
|
5
5
|
function resetsDate(provider) {
|
|
6
6
|
if (!provider.quota)
|
|
@@ -13,6 +13,7 @@ function resetsDate(provider) {
|
|
|
13
13
|
export function ProviderCard({ provider, recommended, asOf, onSelect, }) {
|
|
14
14
|
const quota = provider.quota;
|
|
15
15
|
const advisory = provider.advisory;
|
|
16
|
+
const pace = paceCells(advisory);
|
|
16
17
|
const asOfMs = Date.parse(asOf);
|
|
17
18
|
const updated = ageDuration(provider.ageMs);
|
|
18
19
|
const resetsIn = quota ? timeLeft(quota.resetsAt, asOfMs) : null;
|
|
@@ -22,5 +23,5 @@ export function ProviderCard({ provider, recommended, asOf, onSelect, }) {
|
|
|
22
23
|
e.preventDefault();
|
|
23
24
|
onSelect(provider.id);
|
|
24
25
|
}
|
|
25
|
-
}, className: `pcard ${recommended ? "is-recommended is-top-pick" : ""}`, children: [_jsxs("div", { className: "pcard-head", children: [_jsx("span", { className: "picon", style: providerTint(provider.id), "aria-hidden": "true", children: _jsx(ProviderIcon, { id: provider.id, title: name }) }), _jsxs("div", { className: "who", children: [_jsxs("b", { children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), quota && quota.plan && _jsx("span", { className: "pl", children: quota.plan }), updated && _jsxs("span", { className: "src", children: ["Updated ", updated, " ago"] })] }), _jsx(Badge, { provider: provider })] }), _jsxs("div", { className: "pcard-body", children: [_jsx(PaceBar, { provider: provider, asOf: asOf }), _jsxs("div", { className: "pstats", children: [_jsxs("div", { children: [_jsx("span", { className: "l", children: "
|
|
26
|
+
}, className: `pcard ${recommended ? "is-recommended is-top-pick" : ""}`, children: [_jsxs("div", { className: "pcard-head", children: [_jsx("span", { className: "picon", style: providerTint(provider.id), "aria-hidden": "true", children: _jsx(ProviderIcon, { id: provider.id, title: name }) }), _jsxs("div", { className: "who", children: [_jsxs("b", { children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), quota && quota.plan && _jsx("span", { className: "pl", children: quota.plan }), updated && _jsxs("span", { className: "src", children: ["Updated ", updated, " ago"] })] }), _jsx(Badge, { provider: provider })] }), _jsxs("div", { className: "pcard-body", children: [_jsx(PaceBar, { provider: provider, asOf: asOf }), _jsxs("div", { className: "pstats", children: [_jsxs("div", { title: PACE_TIPS.avg, children: [_jsx("span", { className: "l", children: "Avg pace" }), _jsx("span", { className: "v", children: pace.avg })] }), _jsxs("div", { title: PACE_TIPS.recent, children: [_jsx("span", { className: "l", children: "24h pace" }), _jsx("span", { className: "v", children: pace.recent })] }), _jsxs("div", { title: PACE_TIPS.ideal, children: [_jsx("span", { className: "l", children: "Ideal pace" }), _jsx("span", { className: "v", children: advisory ? `${advisory.idealRate.toFixed(1)}%/day` : "—" })] }), _jsxs("div", { children: [_jsx("span", { className: "l", children: "Resets in" }), _jsx("span", { className: "v", children: resetsIn ? `${resetsIn}${isEstimated(provider) ? " (est.)" : ""}` : resetCountdown(provider, asOfMs) })] })] })] }), _jsxs("div", { className: "pcard-foot", children: [_jsx("span", { children: resetsDate(provider) ?? resetCountdown(provider, asOfMs) }), _jsx("span", { className: "rowbtn", "aria-hidden": "true", children: "Inspect" })] })] }));
|
|
26
27
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { ageDuration, providerSubtitle } from "../state.js";
|
|
4
|
-
import { Badge, PaceBar, barGeometry, isEstimated, paceBadge, resetClock, timeLeft, } from "./PaceBar.js";
|
|
4
|
+
import { Badge, PaceBar, barGeometry, isEstimated, paceBadge, paceFigures, resetClock, timeLeft, } from "./PaceBar.js";
|
|
5
5
|
import { ProviderIcon, providerTint } from "./ProviderIcon.js";
|
|
6
6
|
import { renameProvider } from "../api.js";
|
|
7
7
|
export function exclusionWords(reason) {
|
|
@@ -122,6 +122,8 @@ export function pacingStatusToken(provider) {
|
|
|
122
122
|
return "ahead";
|
|
123
123
|
case "Cap risk":
|
|
124
124
|
return "cap";
|
|
125
|
+
case "Measuring":
|
|
126
|
+
return "measuring";
|
|
125
127
|
case "Not reporting":
|
|
126
128
|
return "out";
|
|
127
129
|
}
|
|
@@ -153,11 +155,21 @@ export function rankingCopy(provider, recommendation) {
|
|
|
153
155
|
if (!advisory || advisory.paceSource === "unknown" || advisory.burnRate === null) {
|
|
154
156
|
pace = modelFailure(provider) ?? "No current pace.";
|
|
155
157
|
}
|
|
156
|
-
else if (advisory.
|
|
157
|
-
pace =
|
|
158
|
+
else if (advisory.remaining <= 0) {
|
|
159
|
+
pace = `Exhausted at ${quota?.usedPct ?? 100}% used.${stamp ? ` Resets ${stamp}.` : ""}`;
|
|
158
160
|
}
|
|
159
161
|
else {
|
|
160
|
-
|
|
162
|
+
const { avg, recent } = paceFigures(advisory);
|
|
163
|
+
const figures = avg !== null && recent !== null && avg.toFixed(1) !== recent.toFixed(1)
|
|
164
|
+
? `${avg.toFixed(1)}%/day window average and ${recent.toFixed(1)}%/day over the last 24h`
|
|
165
|
+
: avg !== null
|
|
166
|
+
? `${avg.toFixed(1)}%/day window average`
|
|
167
|
+
: `${recent.toFixed(1)}%/day over the last 24h`;
|
|
168
|
+
const ideal = `${advisory.idealRate.toFixed(1)}%/day`;
|
|
169
|
+
pace =
|
|
170
|
+
advisory.status === "at risk"
|
|
171
|
+
? `${figures}, against an ideal pace of ${ideal}.`
|
|
172
|
+
: `${figures}. The ideal pace to finish is ${ideal}.`;
|
|
161
173
|
}
|
|
162
174
|
let decision;
|
|
163
175
|
if (provider.exclusionReason) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Badge, PaceBar,
|
|
2
|
+
import { Badge, PaceBar, forecastLine, paceLines, resetCell, } from "./PaceBar.js";
|
|
3
3
|
export function ProviderRow({ provider, recommended, asOf, onSelect, }) {
|
|
4
4
|
const quota = provider.quota;
|
|
5
5
|
const advisory = provider.advisory;
|
|
6
6
|
const asOfMs = Date.parse(asOf);
|
|
7
|
-
const
|
|
7
|
+
const lines = paceLines(advisory);
|
|
8
8
|
const name = provider.displayName;
|
|
9
|
-
return (_jsxs("div", { "data-testid": `provider-row-${provider.id}`, className: `lrow ${recommended ? "is-recommended is-top-pick" : ""}`, onClick: () => onSelect(provider.id), children: [_jsxs("div", { className: "prov", "data-label": "Provider", children: [_jsx("span", { className: "dot", style: { background: `var(--p-${provider.id.split(":")[0]}, var(--surface-raised))` }, "aria-hidden": "true" }), _jsxs("span", { children: [_jsxs("span", { className: "nm", children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), quota?.plan && _jsx("span", { className: "pl", children: quota.plan }), quota?.sessionPct !== undefined && quota.sessionPct !== null && (_jsxs("span", { className: "src", children: ["Session use ", quota.sessionPct, "%"] }))] })] }), _jsx("div", { "data-label": "Used vs elapsed", children: quota ? _jsx(PaceBar, { provider: provider, asOf: asOf }) : "—" }), _jsx("div", { "data-label": "
|
|
9
|
+
return (_jsxs("div", { "data-testid": `provider-row-${provider.id}`, className: `lrow ${recommended ? "is-recommended is-top-pick" : ""}`, onClick: () => onSelect(provider.id), children: [_jsxs("div", { className: "prov", "data-label": "Provider", children: [_jsx("span", { className: "dot", style: { background: `var(--p-${provider.id.split(":")[0]}, var(--surface-raised))` }, "aria-hidden": "true" }), _jsxs("span", { children: [_jsxs("span", { className: "nm", children: [name, recommended && (_jsx("span", { className: "rec-icon", title: "Recommended for next session", role: "img", "aria-label": "recommended", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }) }))] }), quota?.plan && _jsx("span", { className: "pl", children: quota.plan }), quota?.sessionPct !== undefined && quota.sessionPct !== null && (_jsxs("span", { className: "src", children: ["Session use ", quota.sessionPct, "%"] }))] })] }), _jsx("div", { "data-label": "Used vs elapsed", children: quota ? _jsx(PaceBar, { provider: provider, asOf: asOf }) : "—" }), _jsx("div", { "data-label": "Reset", children: _jsx("span", { className: "cell-b", children: resetCell(provider, asOfMs) }) }), _jsxs("div", { "data-label": "Pace", children: [_jsx(Badge, { provider: provider }), lines.length > 0 && (_jsx("div", { style: { marginTop: 2 }, children: lines.map((l) => (_jsxs("div", { className: "cell-s paceline", title: l.tip, children: [_jsx("span", { children: l.label }), _jsx("span", { className: "cell-b", children: l.value })] }, l.label))) }))] }), _jsx("button", { type: "button", className: "rowbtn", onClick: (e) => {
|
|
10
10
|
e.stopPropagation();
|
|
11
11
|
onSelect(provider.id);
|
|
12
12
|
}, children: "Inspect" }), _jsx("span", { className: "sr-only", children: forecastLine(provider) })] }));
|
|
@@ -6,7 +6,7 @@ const RAIL_MS = RAIL_DAYS * 24 * 60 * 60 * 1000;
|
|
|
6
6
|
/** Pins within this span of rail group into one collision window. */
|
|
7
7
|
const COLLISION_PCT = 3;
|
|
8
8
|
/** Pins per window before they group into a cluster pin. */
|
|
9
|
-
const WINDOW_CAPACITY =
|
|
9
|
+
const WINDOW_CAPACITY = 3;
|
|
10
10
|
/**
|
|
11
11
|
* Place reset pins proportionally over the next 7 days from `asOf`.
|
|
12
12
|
* Crowded windows past tier capacity group into clusters; resets beyond the
|
|
@@ -102,5 +102,5 @@ export function SubscriptionList({ providers, recommendation, asOf, lastComplete
|
|
|
102
102
|
const asOfMs = Date.parse(asOf);
|
|
103
103
|
const ranked = enabled.filter((p) => p.exclusionReason === null);
|
|
104
104
|
const excluded = enabled.filter((p) => p.exclusionReason !== null);
|
|
105
|
-
return (_jsxs("section", { "aria-label": "Subscriptions", children: [_jsxs("div", { className: "section-head", children: [_jsx("h2", { children: "All subscriptions" }), _jsxs("div", { className: "section-head-controls", children: [_jsx(SortDropdown, { value: sortKey, onChange: setSortKey }), _jsxs("div", { role: "group", "aria-label": "Display density", className: "seg", children: [_jsxs("button", { type: "button", "aria-pressed": effective === "cards", onClick: () => setDensity("cards"), children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "3", width: "8", height: "8", rx: "2" }), _jsx("rect", { x: "13", y: "3", width: "8", height: "8", rx: "2" }), _jsx("rect", { x: "3", y: "13", width: "8", height: "8", rx: "2" }), _jsx("rect", { x: "13", y: "13", width: "8", height: "8", rx: "2" })] }), "Cards"] }), _jsxs("button", { type: "button", "aria-pressed": effective === "table", onClick: () => setDensity("table"), children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "5", width: "18", height: "2.6", rx: "1.3" }), _jsx("rect", { x: "3", y: "11", width: "18", height: "2.6", rx: "1.3" }), _jsx("rect", { x: "3", y: "17", width: "18", height: "2.6", rx: "1.3" })] }), "Table"] })] })] })] }), effective === "cards" ? (_jsx("div", { "data-testid": "cards-grid", className: "cards cards-grid", children: enabled.map((p) => (_jsx(ProviderCard, { provider: p, recommended: p.id === recommendation.use, asOf: asOf, onSelect: onSelectProvider }, p.id))) })) : (_jsx("div", { className: "table-scroll", children: _jsxs("div", { "data-testid": "ledger-table", className: "ledger", role: "table", children: [_jsxs("div", { className: "lrow lhead", role: "row", children: [_jsx("span", { role: "columnheader", children: "Provider" }), _jsx("span", { role: "columnheader", children: "Used vs elapsed" }), _jsx("span", { role: "columnheader", children: "
|
|
105
|
+
return (_jsxs("section", { "aria-label": "Subscriptions", children: [_jsxs("div", { className: "section-head", children: [_jsx("h2", { children: "All subscriptions" }), _jsxs("div", { className: "section-head-controls", children: [_jsx(SortDropdown, { value: sortKey, onChange: setSortKey }), _jsxs("div", { role: "group", "aria-label": "Display density", className: "seg", children: [_jsxs("button", { type: "button", "aria-pressed": effective === "cards", onClick: () => setDensity("cards"), children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "3", width: "8", height: "8", rx: "2" }), _jsx("rect", { x: "13", y: "3", width: "8", height: "8", rx: "2" }), _jsx("rect", { x: "3", y: "13", width: "8", height: "8", rx: "2" }), _jsx("rect", { x: "13", y: "13", width: "8", height: "8", rx: "2" })] }), "Cards"] }), _jsxs("button", { type: "button", "aria-pressed": effective === "table", onClick: () => setDensity("table"), children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "5", width: "18", height: "2.6", rx: "1.3" }), _jsx("rect", { x: "3", y: "11", width: "18", height: "2.6", rx: "1.3" }), _jsx("rect", { x: "3", y: "17", width: "18", height: "2.6", rx: "1.3" })] }), "Table"] })] })] })] }), effective === "cards" ? (_jsx("div", { "data-testid": "cards-grid", className: "cards cards-grid", children: enabled.map((p) => (_jsx(ProviderCard, { provider: p, recommended: p.id === recommendation.use, asOf: asOf, onSelect: onSelectProvider }, p.id))) })) : (_jsx("div", { className: "table-scroll", children: _jsxs("div", { "data-testid": "ledger-table", className: "ledger", role: "table", children: [_jsxs("div", { className: "lrow lhead", role: "row", children: [_jsx("span", { role: "columnheader", children: "Provider" }), _jsx("span", { role: "columnheader", children: "Used vs elapsed" }), _jsx("span", { role: "columnheader", children: "Reset" }), _jsx("span", { role: "columnheader", children: "Pace" }), _jsx("span", { role: "columnheader" })] }), enabled.map((p) => (_jsx(ProviderRow, { provider: p, recommended: p.id === recommendation.use, asOf: asOf, onSelect: onSelectProvider }, p.id)))] }) })), _jsxs("footer", { className: "health", children: [_jsxs("ul", { children: [_jsxs("li", { className: "legend-rec", children: [_jsx("svg", { className: "legend-star", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z" }) }), _jsx("span", { children: "Recommended to use next" })] }), _jsxs("li", { children: [_jsx("span", { className: "dot", style: { background: "var(--good)" } }), _jsxs("span", { children: [ranked.length, " used in ranking"] })] }), excluded.length > 0 && (_jsxs("li", { children: [_jsx("span", { className: "dot", style: { background: "var(--fill-out)" } }), _jsxs("span", { children: [excluded.length, " not reporting", excluded.length <= 2 ? ` · ${excluded.map((p) => p.displayName).join(", ")}` : ""] })] }))] }), _jsx("span", { children: nextRefreshLabel(lastCompletedPollAt, polling) })] }), disabled.length > 0 && (_jsxs("section", { "aria-label": "Disabled providers", children: [_jsxs("h3", { children: ["Disabled (", disabled.length, ")"] }), _jsx("ul", { children: disabled.map((p) => (_jsxs("li", { style: { fontSize: "var(--t-2)" }, children: [p.displayName, " \u00B7 disabled \u00B7 ", resetCountdown(p, asOfMs)] }, p.id))) })] }))] }));
|
|
106
106
|
}
|
package/dist/web/src/state.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface AdvisoryView {
|
|
|
9
9
|
remaining: number;
|
|
10
10
|
idealRate: number;
|
|
11
11
|
burnRate: number | null;
|
|
12
|
+
/** Window-average pace (used % ÷ days elapsed); null before the window start is known. */
|
|
13
|
+
avgPace: number | null;
|
|
12
14
|
burnMeasured: boolean;
|
|
13
15
|
paceSource: PaceSource;
|
|
14
16
|
daysToExhaust: number | null;
|
|
@@ -45,6 +47,11 @@ export interface AttemptView {
|
|
|
45
47
|
succeededAt: string | null;
|
|
46
48
|
success: boolean;
|
|
47
49
|
failureCategory: FailureCategory;
|
|
50
|
+
diagnosticCode?: string | null;
|
|
51
|
+
summary?: string | null;
|
|
52
|
+
action?: string | null;
|
|
53
|
+
errorDetail?: string | null;
|
|
54
|
+
error?: string | null;
|
|
48
55
|
}
|
|
49
56
|
export interface ProviderView {
|
|
50
57
|
id: string;
|