quotacap 0.0.39 → 0.0.41
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/dist/adapters/agy.js +2 -2
- package/dist/adapters/claude.js +1 -1
- package/dist/adapters/codex.js +29 -20
- package/dist/adapters/grok.js +1 -1
- package/dist/adapters/kimi.d.ts +41 -0
- package/dist/adapters/kimi.js +453 -21
- package/dist/adapters/manual.js +2 -2
- package/dist/adapters/muse.js +3 -3
- package/dist/adapters/opencode-go.js +30 -2
- package/dist/adapters/pty.d.ts +9 -0
- package/dist/adapters/pty.js +16 -0
- package/dist/adapters/types.d.ts +17 -2
- package/dist/advisory/engine.d.ts +4 -2
- package/dist/advisory/engine.js +55 -14
- package/dist/advisory/snapshot.js +11 -3
- package/dist/advisory/types.d.ts +11 -3
- package/dist/cli/advise.js +1 -0
- package/dist/cli/clients.d.ts +4 -0
- package/dist/cli/models.js +5 -1
- package/dist/cli/progress.d.ts +32 -0
- package/dist/cli/progress.js +70 -0
- package/dist/cli/runtime.d.ts +5 -0
- package/dist/cli/runtime.js +18 -8
- package/dist/cli/status.js +1 -0
- package/dist/cli/takeover.d.ts +3 -0
- package/dist/cli/takeover.js +7 -1
- package/dist/cli/update.d.ts +4 -0
- package/dist/cli/update.js +8 -1
- package/dist/diagnostics/failure.js +46 -11
- package/dist/format/rows.d.ts +6 -1
- package/dist/format/rows.js +27 -4
- package/dist/format/terminal.js +5 -4
- package/dist/http/server.js +3 -3
- package/dist/runtime/poll.js +1 -1
- package/dist/service/index.js +3 -1
- package/dist/service/macos.d.ts +2 -0
- package/dist/service/macos.js +4 -1
- package/dist/service/systemd.d.ts +2 -0
- package/dist/service/systemd.js +5 -3
- package/dist/store/db.js +12 -1
- package/dist/store/quotas.js +33 -8
- package/dist/version.js +1 -1
- package/dist/web/src/components/AdviceDrawer.js +10 -5
- package/dist/web/src/components/FaultBanner.d.ts +10 -5
- package/dist/web/src/components/FaultBanner.js +19 -9
- package/dist/web/src/components/PaceBar.d.ts +9 -5
- package/dist/web/src/components/PaceBar.js +32 -10
- package/dist/web/src/components/ProviderCard.js +4 -2
- package/dist/web/src/components/ProviderDrawer.d.ts +7 -1
- package/dist/web/src/components/ProviderDrawer.js +46 -9
- package/dist/web/src/components/ProviderRow.js +4 -4
- package/dist/web/src/components/Recommendation.d.ts +5 -2
- package/dist/web/src/components/Recommendation.js +8 -4
- package/dist/web/src/components/ResetRail.js +3 -2
- package/dist/web/src/components/SubscriptionList.d.ts +2 -1
- package/dist/web/src/components/SubscriptionList.js +8 -5
- package/dist/web/src/components/WindowsRow.d.ts +30 -0
- package/dist/web/src/components/WindowsRow.js +68 -0
- package/dist/web/src/pages/Onboarding.js +2 -1
- package/dist/web/src/state.d.ts +12 -0
- package/dist/webAssets.js +1 -1
- package/dist/webHtml.js +1 -1
- package/package.json +1 -1
- package/web/dist/assets/index-BUK8xUHm.js +12 -0
- package/web/dist/assets/{index-NtT9XTrm.css → index-Dk6l5h27.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-CD8jaN87.js +0 -12
|
@@ -3,12 +3,16 @@ import { ageLabel } from "../state.js";
|
|
|
3
3
|
import { timeLeft } from "./PaceBar.js";
|
|
4
4
|
import { ProviderIcon, providerTint } from "./ProviderIcon.js";
|
|
5
5
|
/**
|
|
6
|
-
* Lane membership from server urgency only. Any exclusionReason
|
|
7
|
-
* provider out of both lanes; "on track"
|
|
6
|
+
* Lane membership from server urgency only. Any exclusionReason, or an
|
|
7
|
+
* exhausted binding month, keeps the provider out of both lanes; "on track"
|
|
8
|
+
* belongs to neither lane.
|
|
8
9
|
*/
|
|
9
10
|
export function laneOf(advisory, exclusionReason) {
|
|
10
11
|
if (exclusionReason !== null)
|
|
11
12
|
return null;
|
|
13
|
+
// A blocked month vetoes weekly leftover: no lane, exactly like an exclusion.
|
|
14
|
+
if (advisory.bindingWindow === "monthly" && advisory.bindingRemaining === 0)
|
|
15
|
+
return null;
|
|
12
16
|
switch (advisory.urgency) {
|
|
13
17
|
case "burn now":
|
|
14
18
|
case "use soon":
|
|
@@ -45,7 +49,7 @@ function LaneSection({ testId, label, tintClass, laneClass, icon, members, provi
|
|
|
45
49
|
const quota = byId.get(m.provider)?.quota;
|
|
46
50
|
const left = quota ? timeLeft(quota.resetsAt, asOfMs) : null;
|
|
47
51
|
const name = byId.get(m.provider)?.displayName ?? m.provider;
|
|
48
|
-
return (_jsxs("div", { className: "rsub", children: [_jsx("span", { className: "sq", style: providerTint(m.provider), "aria-hidden": "true", children: _jsx(ProviderIcon, { id: m.provider, title: name, size: 13 }) }), _jsx("div", { className: "rnm-group", children: _jsx("button", { type: "button", className: "rnm", onClick: () => onSelectProvider?.(m.provider), "aria-label": `View ${name} details`, children: name }) }), _jsx("span", { className: "rnum", children: quota ? (_jsxs(_Fragment, { children: [_jsxs("b", { children: [quota.
|
|
52
|
+
return (_jsxs("div", { className: "rsub", children: [_jsx("span", { className: "sq", style: providerTint(m.provider), "aria-hidden": "true", children: _jsx(ProviderIcon, { id: m.provider, title: name, size: 13 }) }), _jsx("div", { className: "rnm-group", children: _jsx("button", { type: "button", className: "rnm", onClick: () => onSelectProvider?.(m.provider), "aria-label": `View ${name} details`, children: name }) }), _jsx("span", { className: "rnum", children: quota ? (_jsxs(_Fragment, { children: [_jsxs("b", { children: [quota.weeklyPct, "%"] }), " used", left ? (_jsxs(_Fragment, { children: [" · ", _jsx("b", { children: left }), " left"] })) : null] })) : null })] }, m.provider));
|
|
49
53
|
}) })] }));
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
@@ -77,7 +81,7 @@ export function Recommendation({ recommendation, providers, asOf, onSelectProvid
|
|
|
77
81
|
if (isClear) {
|
|
78
82
|
return (_jsxs("section", { "aria-label": "Quota advice and pacing", children: [head, _jsxs("section", { "data-testid": "recommendation", "aria-label": "Recommendation", className: "recwrap is-clear", role: "status", children: [_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z" }), _jsx("path", { d: "M8 12.5l2.8 2.8L16 10" })] }), _jsxs("div", { children: [_jsx("b", { children: "No subscription needs priority" }), _jsx("span", { "data-testid": "rec-prose", children: recommendation.reason })] })] })] }));
|
|
79
83
|
}
|
|
80
|
-
return (_jsxs("section", { "aria-label": "Quota advice and pacing", children: [head, _jsxs("section", { "data-testid": "recommendation", "aria-label": "Recommendation", className: "recwrap", role: "status", children: [_jsxs("div", { className: "rec-top", children: [_jsxs("div", { className: "rec-lead", children: [_jsxs("span", { className: "rec-badge", children: [_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", 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" }) }), "Recommendation"] }), recommendation.use === "none" ? (_jsx("p", { className: "rec-advice-prose", "data-testid": "rec-prose", children: recommendation.reason })) : (_jsxs("p", { className: "rec-advice-prose", "data-testid": "rec-prose", children: ["Switch to ", _jsx("strong", { children: pick?.displayName ?? recommendation.use }), " next", recommendation.wastePct !== null ? (_jsxs(_Fragment, { children: [" — ", _jsxs("strong", { children: [Math.round(recommendation.wastePct), "%"] }), " unused quota forecast", pickLeft ? _jsxs(_Fragment, { children: [" to expire in ", _jsx("strong", { children: pickLeft })] }) : null, "."] })) : (_jsxs(_Fragment, { children: [" \u2014 ", recommendation.reason] }))] })), measuring && (_jsx("span", { className: "rec-badge", "data-testid": "rec-measuring", style: {
|
|
84
|
+
return (_jsxs("section", { "aria-label": "Quota advice and pacing", children: [head, _jsxs("section", { "data-testid": "recommendation", "aria-label": "Recommendation", className: "recwrap", role: "status", children: [_jsxs("div", { className: "rec-top", children: [_jsxs("div", { className: "rec-lead", children: [_jsxs("span", { className: "rec-badge", children: [_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", 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" }) }), "Recommendation"] }), recommendation.use === "none" ? (_jsx("p", { className: "rec-advice-prose", "data-testid": "rec-prose", children: recommendation.reason })) : (_jsxs("p", { className: "rec-advice-prose", "data-testid": "rec-prose", children: ["Switch to ", _jsx("strong", { children: pick?.displayName ?? recommendation.use }), " next", recommendation.wastePct !== null && recommendation.bindingWindow !== "monthly" ? (_jsxs(_Fragment, { children: [" — ", _jsxs("strong", { children: [Math.round(recommendation.wastePct), "%"] }), " unused quota forecast", pickLeft ? _jsxs(_Fragment, { children: [" to expire in ", _jsx("strong", { children: pickLeft })] }) : null, "."] })) : (_jsxs(_Fragment, { children: [" \u2014 ", recommendation.reason] }))] })), measuring && (_jsx("span", { className: "rec-badge", "data-testid": "rec-measuring", style: {
|
|
81
85
|
background: "var(--ahead-soft)",
|
|
82
86
|
color: "var(--ahead)",
|
|
83
87
|
borderColor: "var(--ahead)",
|
|
@@ -112,7 +112,7 @@ function ClusterPopover({ cluster, providers, asOfMs, onSelect, onClose, }) {
|
|
|
112
112
|
return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
|
|
113
113
|
onSelect(id);
|
|
114
114
|
onClose();
|
|
115
|
-
}, children: [_jsx("b", { children: name }), _jsxs("span", { children: [provider?.quota ? `${provider.quota.
|
|
115
|
+
}, children: [_jsx("b", { children: name }), _jsxs("span", { children: [provider?.quota ? `${provider.quota.weeklyPct}% · ` : "", provider ? resetCountdown(provider, asOfMs) : ""] })] }) }, id));
|
|
116
116
|
}) }), _jsx("button", { type: "button", className: "btn btn-sm btn-quiet", style: { marginTop: "var(--s2)", width: "100%" }, onClick: onClose, children: "Close" })] }));
|
|
117
117
|
}
|
|
118
118
|
function tzAbbrev() {
|
|
@@ -136,6 +136,7 @@ function pinPace(provider) {
|
|
|
136
136
|
return { cls: "pace-ahead", color: "var(--ahead)" };
|
|
137
137
|
case "Watch":
|
|
138
138
|
return { cls: "pace-watch", color: "var(--watch)" };
|
|
139
|
+
case "Monthly exhausted":
|
|
139
140
|
case "Cap risk":
|
|
140
141
|
return { cls: "pace-cap", color: "var(--danger)" };
|
|
141
142
|
default:
|
|
@@ -177,7 +178,7 @@ export function ResetRail({ providers, asOf, onSelectProvider, }) {
|
|
|
177
178
|
return (_jsxs("button", { "data-testid": "pin", type: "button", className: `pin ${pin.band} ${tierClass} ${alignClass}`, style: {
|
|
178
179
|
left: `${pin.positionPct}%`,
|
|
179
180
|
"--dot-color": paceColor,
|
|
180
|
-
}, "aria-label": `${name}: ${pace}, resets ${when}`, onClick: () => onSelectProvider(pin.id), children: [pin.band === "above" ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "pin-when", children: pin.estimated ? `${when} (est.)` : when }), _jsx("span", { className: `pin-label ${paceCls}`, style: pinBorder, children: name }), _jsx("span", { className: "pin-stem" }), _jsx("span", { className: "pin-dot" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "pin-dot" }), _jsx("span", { className: "pin-stem" }), _jsx("span", { className: `pin-label ${paceCls}`, style: pinBorder, children: name }), _jsx("span", { className: "pin-when", children: pin.estimated ? `${when} (est.)` : when })] })), _jsxs("span", { className: "pin-tooltip", role: "tooltip", children: [_jsx("b", { children: name }), _jsx("span", { className: "pt-sep", children: "\u00B7" }), provider?.quota ? `${provider.quota.
|
|
181
|
+
}, "aria-label": `${name}: ${pace}, resets ${when}`, onClick: () => onSelectProvider(pin.id), children: [pin.band === "above" ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "pin-when", children: pin.estimated ? `${when} (est.)` : when }), _jsx("span", { className: `pin-label ${paceCls}`, style: pinBorder, children: name }), _jsx("span", { className: "pin-stem" }), _jsx("span", { className: "pin-dot" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "pin-dot" }), _jsx("span", { className: "pin-stem" }), _jsx("span", { className: `pin-label ${paceCls}`, style: pinBorder, children: name }), _jsx("span", { className: "pin-when", children: pin.estimated ? `${when} (est.)` : when })] })), _jsxs("span", { className: "pin-tooltip", role: "tooltip", children: [_jsx("b", { children: name }), _jsx("span", { className: "pt-sep", children: "\u00B7" }), provider?.quota ? `${provider.quota.weeklyPct}% used` : "no readings", _jsx("span", { className: "pt-sep", children: "\u00B7" }), left ? `${left} left` : when, pin.estimated ? " (est.)" : ""] })] }, pin.id));
|
|
181
182
|
}), placement.clusters.map((cluster, index) => {
|
|
182
183
|
const alignClass = cluster.positionPct < 12 ? "pin-start" : cluster.positionPct > 88 ? "pin-end" : "";
|
|
183
184
|
const tierClass = cluster.tier > 0 ? `tier${cluster.tier + 1}` : "tier1";
|
|
@@ -4,7 +4,8 @@ export type SortKey = "recommended" | "reset-asc" | "reset-desc" | "used-desc" |
|
|
|
4
4
|
export type Density = "cards" | "table";
|
|
5
5
|
/**
|
|
6
6
|
* Sort over server fields only. Recommended puts the server pick first, then
|
|
7
|
-
* lane members, then the rest in server order, excluded last.
|
|
7
|
+
* lane members, then the rest in server order, excluded last. A blocked month
|
|
8
|
+
* sorts with the excluded rows. Sort is stable.
|
|
8
9
|
*/
|
|
9
10
|
export declare function sortProviders(providers: ProviderView[], key: SortKey, recommendation: RecommendationView): ProviderView[];
|
|
10
11
|
/** Density follows the control at every width — the prototype keeps Cards and Table visible on mobile. */
|
|
@@ -7,7 +7,8 @@ import { ProviderRow } from "./ProviderRow.js";
|
|
|
7
7
|
import { resetCountdown } from "./PaceBar.js";
|
|
8
8
|
/**
|
|
9
9
|
* Sort over server fields only. Recommended puts the server pick first, then
|
|
10
|
-
* lane members, then the rest in server order, excluded last.
|
|
10
|
+
* lane members, then the rest in server order, excluded last. A blocked month
|
|
11
|
+
* sorts with the excluded rows. Sort is stable.
|
|
11
12
|
*/
|
|
12
13
|
export function sortProviders(providers, key, recommendation) {
|
|
13
14
|
const list = [...providers];
|
|
@@ -21,15 +22,17 @@ export function sortProviders(providers, key, recommendation) {
|
|
|
21
22
|
if (!order.has(a.provider))
|
|
22
23
|
order.set(a.provider, rank++);
|
|
23
24
|
}
|
|
25
|
+
const blocked = (p) => p.exclusionReason !== null ||
|
|
26
|
+
(p.advisory?.bindingWindow === "monthly" && p.advisory.bindingRemaining === 0);
|
|
24
27
|
const ranked = list
|
|
25
|
-
.filter((p) => p
|
|
28
|
+
.filter((p) => !blocked(p) && order.has(p.id))
|
|
26
29
|
.sort((a, b) => order.get(a.id) - order.get(b.id));
|
|
27
|
-
const rest = list.filter((p) => p
|
|
28
|
-
const excluded = list.filter(
|
|
30
|
+
const rest = list.filter((p) => !blocked(p) && !order.has(p.id));
|
|
31
|
+
const excluded = list.filter(blocked);
|
|
29
32
|
return [...ranked, ...rest, ...excluded];
|
|
30
33
|
}
|
|
31
34
|
const resetMs = (p) => (p.quota ? Date.parse(p.quota.resetsAt) : NaN);
|
|
32
|
-
const used = (p) => (p.quota ? p.quota.
|
|
35
|
+
const used = (p) => (p.quota ? p.quota.weeklyPct : NaN);
|
|
33
36
|
const withInvalidLast = (cmp, value) => list.sort((a, b) => {
|
|
34
37
|
const va = value(a);
|
|
35
38
|
const vb = value(b);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ProviderView } from "../state.js";
|
|
3
|
+
/** One-unit countdown for a window tag: "7d", "4h" or "40m". Null when passed or unparseable. */
|
|
4
|
+
export declare function windowCountdown(resetsAt: string, asOfMs: number): string | null;
|
|
5
|
+
export interface WindowUnit {
|
|
6
|
+
win: "five" | "month";
|
|
7
|
+
tag: "5h" | "Mth";
|
|
8
|
+
cardLabel: string;
|
|
9
|
+
rowLabel: string;
|
|
10
|
+
pct: number;
|
|
11
|
+
/** Presentation-only heat: < 80 none, 80–99 hot, 100 or exhausted full. */
|
|
12
|
+
heat: "" | "hot" | "full";
|
|
13
|
+
/** Tag time outside the pill: "in 7d", or the weekday alone when the month is exhausted. */
|
|
14
|
+
time: string | null;
|
|
15
|
+
/** Expanded card track's reset line. */
|
|
16
|
+
reset: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** Present windows only; a unit with no data is not rendered at all. */
|
|
19
|
+
export declare function windowUnits(provider: ProviderView, asOfMs: number): WindowUnit[];
|
|
20
|
+
/**
|
|
21
|
+
* Collapsed windows line under the weekly bar, with a per-card chevron that
|
|
22
|
+
* expands thin tracks. A real button inside the card's role="button": click
|
|
23
|
+
* stops propagation so it never opens the drawer, and the card's own keydown
|
|
24
|
+
* ignores keys aimed at this button.
|
|
25
|
+
*/
|
|
26
|
+
export declare function WindowsRow({ provider, asOf, variant }: {
|
|
27
|
+
provider: ProviderView;
|
|
28
|
+
asOf: string;
|
|
29
|
+
variant: "card" | "row";
|
|
30
|
+
}): React.JSX.Element | null;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { resetClock, resetWeekday } from "./PaceBar.js";
|
|
4
|
+
const MIN_MS = 60_000;
|
|
5
|
+
const HOUR_MS = 60 * MIN_MS;
|
|
6
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
7
|
+
/** One-unit countdown for a window tag: "7d", "4h" or "40m". Null when passed or unparseable. */
|
|
8
|
+
export function windowCountdown(resetsAt, asOfMs) {
|
|
9
|
+
const diff = Date.parse(resetsAt) - asOfMs;
|
|
10
|
+
if (!Number.isFinite(diff) || diff <= 0)
|
|
11
|
+
return null;
|
|
12
|
+
if (diff >= DAY_MS)
|
|
13
|
+
return `${Math.floor(diff / DAY_MS)}d`;
|
|
14
|
+
if (diff >= HOUR_MS)
|
|
15
|
+
return `${Math.floor(diff / HOUR_MS)}h`;
|
|
16
|
+
return `${Math.max(1, Math.floor(diff / MIN_MS))}m`;
|
|
17
|
+
}
|
|
18
|
+
function heatOf(pct, exhausted) {
|
|
19
|
+
if (exhausted || pct >= 100)
|
|
20
|
+
return "full";
|
|
21
|
+
return pct >= 80 ? "hot" : "";
|
|
22
|
+
}
|
|
23
|
+
/** Present windows only; a unit with no data is not rendered at all. */
|
|
24
|
+
export function windowUnits(provider, asOfMs) {
|
|
25
|
+
const q = provider.quota;
|
|
26
|
+
if (!q)
|
|
27
|
+
return [];
|
|
28
|
+
const units = [];
|
|
29
|
+
if (typeof q.fiveHourPct === "number" && Number.isFinite(q.fiveHourPct)) {
|
|
30
|
+
// No adapter reports a 5h reset, so the 5h tag carries no time.
|
|
31
|
+
units.push({ win: "five", tag: "5h", cardLabel: "5h limit", rowLabel: "5h",
|
|
32
|
+
pct: q.fiveHourPct, heat: heatOf(q.fiveHourPct, false), time: null, reset: null });
|
|
33
|
+
}
|
|
34
|
+
if (q.monthlyKind === "included" && typeof q.monthlyPct === "number" && q.monthlyResetsAt) {
|
|
35
|
+
const exhausted = q.monthlyStatus === "exhausted";
|
|
36
|
+
const left = windowCountdown(q.monthlyResetsAt, asOfMs);
|
|
37
|
+
const clock = resetClock(q.monthlyResetsAt);
|
|
38
|
+
units.push({ win: "month", tag: "Mth", cardLabel: "Monthly", rowLabel: "Month",
|
|
39
|
+
pct: q.monthlyPct, heat: heatOf(q.monthlyPct, exhausted),
|
|
40
|
+
time: exhausted ? resetWeekday(q.monthlyResetsAt) : left ? `in ${left}` : null,
|
|
41
|
+
reset: exhausted ? (clock ? `resets ${clock}` : null) : left ? `in ${left}` : null });
|
|
42
|
+
}
|
|
43
|
+
return units;
|
|
44
|
+
}
|
|
45
|
+
function trackFill(u) {
|
|
46
|
+
const cap = u.pct >= 80 || u.heat === "full";
|
|
47
|
+
return {
|
|
48
|
+
width: `${Math.min(100, Math.max(0, u.pct))}%`,
|
|
49
|
+
background: cap ? "var(--fill-cap)" : "var(--fill-ontrack)",
|
|
50
|
+
["--fill-edge"]: cap ? "var(--edge-cap)" : "var(--edge-ontrack)",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Collapsed windows line under the weekly bar, with a per-card chevron that
|
|
55
|
+
* expands thin tracks. A real button inside the card's role="button": click
|
|
56
|
+
* stops propagation so it never opens the drawer, and the card's own keydown
|
|
57
|
+
* ignores keys aimed at this button.
|
|
58
|
+
*/
|
|
59
|
+
export function WindowsRow({ provider, asOf, variant }) {
|
|
60
|
+
const [open, setOpen] = React.useState(false);
|
|
61
|
+
const units = windowUnits(provider, Date.parse(asOf));
|
|
62
|
+
if (units.length === 0)
|
|
63
|
+
return null;
|
|
64
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "wline", "data-testid": `wline-${provider.id}`, "aria-expanded": open, "aria-label": `${provider.displayName} other windows: ${units.map((u) => `${u.tag} ${u.pct}%${u.time ? ` ${u.time}` : ""}`).join(", ")}`, onClick: (e) => {
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
setOpen((v) => !v);
|
|
67
|
+
}, children: [units.map((u) => (_jsxs("span", { className: "unit", "data-win": u.win, children: [_jsxs("span", { className: u.heat ? `seg ${u.heat}` : "seg", children: [_jsx("span", { className: "k", children: u.tag }), _jsxs("span", { className: "n", children: [u.pct, "%"] })] }), u.time && _jsx("span", { className: "t", children: u.time })] }, u.win))), _jsx("span", { className: "chev", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", children: _jsx("path", { d: "M4 6l4 4 4-4" }) }) })] }), open && (_jsx("div", { className: "windows", "data-testid": `windows-${provider.id}`, children: units.map((u) => (_jsxs("div", { className: "win", "data-win": u.win, children: [_jsx("span", { className: "lab", children: variant === "card" ? u.cardLabel : u.rowLabel }), _jsx("div", { className: "track", children: _jsx("div", { className: "fill", style: trackFill(u) }) }), _jsxs("span", { className: "pct", children: [u.pct, "%"] }), variant === "card" && u.reset && _jsx("span", { className: "reset", children: u.reset })] }, u.win))) }))] }));
|
|
68
|
+
}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { ageDuration } from "../state.js";
|
|
4
4
|
import { failureWords } from "../components/ProviderDrawer.js";
|
|
5
|
+
import { authInstruction } from "../components/FaultBanner.js";
|
|
5
6
|
/**
|
|
6
7
|
* Provider readiness from server state only. The browser cannot probe local
|
|
7
8
|
* paths, so this reports what the daemon reported — never a local scan.
|
|
@@ -27,7 +28,7 @@ export function readiness(provider) {
|
|
|
27
28
|
if (provider.lastAttempt?.failureCategory === "auth") {
|
|
28
29
|
return {
|
|
29
30
|
status: "Sign in first",
|
|
30
|
-
detail:
|
|
31
|
+
detail: authInstruction(cli, provider.lastAttempt),
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
if (!provider.lastAttempt) {
|
package/dist/web/src/state.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface AdvisoryView {
|
|
|
23
23
|
status: BurnStatus;
|
|
24
24
|
wastePct: number | null;
|
|
25
25
|
urgency: Urgency;
|
|
26
|
+
bindingWindow: "weekly" | "monthly";
|
|
27
|
+
bindingRemaining: number;
|
|
28
|
+
bindingDaysLeft: number;
|
|
26
29
|
}
|
|
27
30
|
export interface RecommendationView {
|
|
28
31
|
use: string;
|
|
@@ -30,6 +33,7 @@ export interface RecommendationView {
|
|
|
30
33
|
wastePct: number | null;
|
|
31
34
|
idealRate: number;
|
|
32
35
|
recommendationBasis: RecommendationBasis;
|
|
36
|
+
bindingWindow: "weekly" | "monthly";
|
|
33
37
|
alternatives: unknown[];
|
|
34
38
|
advisories: AdvisoryView[];
|
|
35
39
|
}
|
|
@@ -48,7 +52,15 @@ export interface WindowCloseView {
|
|
|
48
52
|
export interface QuotaView {
|
|
49
53
|
provider: string;
|
|
50
54
|
plan: string;
|
|
55
|
+
weeklyPct: number;
|
|
56
|
+
fiveHourPct?: number;
|
|
57
|
+
monthlyPct?: number;
|
|
58
|
+
monthlyResetsAt?: string;
|
|
59
|
+
monthlyStatus?: "ok" | "exhausted";
|
|
60
|
+
monthlyKind?: "included";
|
|
61
|
+
/** @deprecated wire alias for weeklyPct. */
|
|
51
62
|
usedPct: number;
|
|
63
|
+
/** @deprecated wire alias for fiveHourPct. */
|
|
52
64
|
sessionPct?: number;
|
|
53
65
|
resetsAt: string;
|
|
54
66
|
periodStart: string;
|