mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Streamline sidebar usage and stacked sheet interactions (#5805)
* feat(app): streamline sidebar usage and stacked sheets * fix(app): restore compact sidebar navigation clicks The close button container emitted box-none through Unistyles as invalid CSS and intercepted New workspace clicks. Let React Native apply click-through behavior at the view boundary. Keep the import journey on its existing New workspace screen and assert only the visible transcript. * fix(app): cap menu sheets at eighty percent height Keep long sidebar usage reports recognizably sheet-sized while allowing the content to scroll. Short menu sheets retain dynamic sizing.
This commit is contained in:
@@ -307,6 +307,38 @@ describe("Claude usage source scoped weekly limits", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("summarizes the session and weekly windows by default, not scoped ones", async () => {
|
||||
const { provider } = claudeProvider({
|
||||
five_hour: { utilization: 6, resets_at: "2026-06-01T21:00:00Z" },
|
||||
seven_day: { utilization: 23, resets_at: "2026-06-04T00:00:00Z" },
|
||||
limits: [fableLimit()],
|
||||
});
|
||||
|
||||
const usage = await provider.fetchUsage();
|
||||
|
||||
expect(usage.windows.map((window) => [window.id, window.summary ?? false])).toEqual([
|
||||
["five_hour", true],
|
||||
["weekly", true],
|
||||
["weekly_model_fable", false],
|
||||
]);
|
||||
});
|
||||
|
||||
it("gives every window a short label for tight spaces", async () => {
|
||||
const { provider } = claudeProvider({
|
||||
five_hour: { utilization: 6, resets_at: "2026-06-01T21:00:00Z" },
|
||||
seven_day: { utilization: 23, resets_at: "2026-06-04T00:00:00Z" },
|
||||
limits: [fableLimit()],
|
||||
});
|
||||
|
||||
const usage = await provider.fetchUsage();
|
||||
|
||||
expect(usage.windows.map((window) => [window.id, window.shortLabel])).toEqual([
|
||||
["five_hour", "5h"],
|
||||
["weekly", "wk"],
|
||||
["weekly_model_fable", "wk Fable"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders a scoped window that is at zero and inactive", async () => {
|
||||
const { provider } = claudeProvider({
|
||||
seven_day: { utilization: 23, resets_at: "2026-06-04T00:00:00Z" },
|
||||
|
||||
@@ -112,9 +112,11 @@ const UNSCOPED_WINDOWS: ReadonlyArray<{
|
||||
field: "five_hour" | "seven_day";
|
||||
id: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
summary: boolean;
|
||||
}> = [
|
||||
{ field: "five_hour", id: "five_hour", label: "Session" },
|
||||
{ field: "seven_day", id: "weekly", label: "Weekly" },
|
||||
{ field: "five_hour", id: "five_hour", label: "Session", shortLabel: "5h", summary: true },
|
||||
{ field: "seven_day", id: "weekly", label: "Weekly", shortLabel: "wk", summary: true },
|
||||
];
|
||||
|
||||
// Scoped windows from before `limits[]` existed. Declaring the dimension here is what
|
||||
@@ -253,6 +255,8 @@ function unscopedWindows(resp: ClaudeUsageResponse): UsageWindow[] {
|
||||
windowFromUsedPct({
|
||||
id: spec.id,
|
||||
label: spec.label,
|
||||
shortLabel: spec.shortLabel,
|
||||
summary: spec.summary,
|
||||
utilizationPct: window.utilization,
|
||||
resetsAt: window.resets_at ?? null,
|
||||
tone: toneFromUsedPct(window.utilization),
|
||||
@@ -272,6 +276,7 @@ function scopedWindows(limits: ScopedLimit[]): UsageWindow[] {
|
||||
return windowFromUsedPct({
|
||||
id,
|
||||
label: `Weekly \u00b7 ${limit.name}`,
|
||||
shortLabel: `wk ${limit.name}`,
|
||||
utilizationPct: limit.usedPct,
|
||||
resetsAt: limit.resetsAt,
|
||||
tone: toneFromUsedPct(limit.usedPct),
|
||||
|
||||
@@ -103,7 +103,31 @@ test("coerces credit balance and marks a 96 percent window dangerous", async ()
|
||||
expect.objectContaining({ id: "weekly", tone: "danger" }),
|
||||
]),
|
||||
);
|
||||
expect(report.balances).toEqual([expect.objectContaining({ remaining: 0, tone: "danger" })]);
|
||||
expect(report.balances).toEqual([
|
||||
expect.objectContaining({ remaining: 0, unit: "credits", tone: "danger" }),
|
||||
]);
|
||||
});
|
||||
|
||||
test("summarizes the session and weekly windows by default, not code review", async () => {
|
||||
const report = await fetchUsage(
|
||||
{},
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
rate_limit: {
|
||||
primary_window: { used_percent: 12 },
|
||||
secondary_window: { used_percent: 40 },
|
||||
},
|
||||
code_review_rate_limit: { primary_window: { used_percent: 5 } },
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
);
|
||||
expect(report.windows.map((window) => [window.id, window.summary ?? false])).toEqual([
|
||||
["session", true],
|
||||
["weekly", true],
|
||||
["code_review", false],
|
||||
]);
|
||||
});
|
||||
|
||||
test("HTML usage body is unavailable", async () => {
|
||||
|
||||
@@ -57,15 +57,13 @@ export async function readAuth(
|
||||
}
|
||||
|
||||
function usageWindow(
|
||||
id: string,
|
||||
label: string,
|
||||
spec: { id: string; label: string; shortLabel: string; summary?: boolean },
|
||||
value: z.infer<typeof windowSchema> | null | undefined,
|
||||
): UsageWindow | null {
|
||||
if (!value) return null;
|
||||
const usedPct = value.used_percent ?? 0;
|
||||
return windowFromUsedPct({
|
||||
id,
|
||||
label,
|
||||
...spec,
|
||||
utilizationPct: usedPct,
|
||||
resetsAt: value.reset_at != null ? new Date(value.reset_at * 1000).toISOString() : null,
|
||||
tone: toneFromUsedPct(usedPct),
|
||||
@@ -95,9 +93,18 @@ export async function fetchUsage(
|
||||
if (text.trim().startsWith("<")) return { status: "unavailable", windows: [] };
|
||||
const usage = responseSchema.parse(JSON.parse(text));
|
||||
const windows = [
|
||||
usageWindow("session", "Session", usage.rate_limit?.primary_window),
|
||||
usageWindow("weekly", "Weekly", usage.rate_limit?.secondary_window),
|
||||
usageWindow("code_review", "Code review", usage.code_review_rate_limit?.primary_window),
|
||||
usageWindow(
|
||||
{ id: "session", label: "Session", shortLabel: "5h", summary: true },
|
||||
usage.rate_limit?.primary_window,
|
||||
),
|
||||
usageWindow(
|
||||
{ id: "weekly", label: "Weekly", shortLabel: "wk", summary: true },
|
||||
usage.rate_limit?.secondary_window,
|
||||
),
|
||||
usageWindow(
|
||||
{ id: "code_review", label: "Code review", shortLabel: "review" },
|
||||
usage.code_review_rate_limit?.primary_window,
|
||||
),
|
||||
].filter((window): window is UsageWindow => window !== null);
|
||||
const balance = usage.credits?.balance;
|
||||
return {
|
||||
@@ -112,7 +119,7 @@ export async function fetchUsage(
|
||||
id: "credits",
|
||||
label: "Credits",
|
||||
remaining: balance,
|
||||
unit: "usd",
|
||||
unit: "credits",
|
||||
tone: balanceToneFromRemaining(balance),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -98,6 +98,7 @@ function grokUsageWindow(response: z.infer<typeof GrokUsageResponseSchema>): Usa
|
||||
return windowFromUsedPct({
|
||||
id: weekly ? "weekly" : "monthly",
|
||||
label: weekly ? "Weekly" : "Monthly",
|
||||
shortLabel: weekly ? "wk" : "mo",
|
||||
utilizationPct: percent,
|
||||
resetsAt: period?.end ?? null,
|
||||
tone: toneFromUsedPct(percent),
|
||||
|
||||
@@ -78,6 +78,8 @@ export class Usage {
|
||||
windowFromUsedPct({
|
||||
id: "five_hour",
|
||||
label: `${usage.window.windowDurationMins / 60} hours`,
|
||||
shortLabel: `${usage.window.windowDurationMins / 60}h`,
|
||||
summary: true,
|
||||
utilizationPct: usage.window.usedPercent,
|
||||
resetsAt: new Date(usage.window.resetsAtMs).toISOString(),
|
||||
tone: toneFromUsedPct(usage.window.usedPercent),
|
||||
@@ -85,6 +87,8 @@ export class Usage {
|
||||
windowFromUsedPct({
|
||||
id: "weekly",
|
||||
label: "Weekly",
|
||||
shortLabel: "wk",
|
||||
summary: true,
|
||||
utilizationPct: usage.weekly.usedPercent,
|
||||
resetsAt: new Date(usage.weekly.resetsAtMs).toISOString(),
|
||||
tone: toneFromUsedPct(usage.weekly.usedPercent),
|
||||
|
||||
@@ -59,6 +59,8 @@ test("discovers and fetches the default key from read-only auth.json", async ()
|
||||
["weekly", 42, "2026-09-28T00:00:00.000Z"],
|
||||
["monthly", 100, "2026-10-01T00:00:00.000Z"],
|
||||
]);
|
||||
// The rolling window's length is not reported, so its percent stands without a name.
|
||||
expect(report.windows.map((window) => window.shortLabel)).toEqual(["", "wk", "mo"]);
|
||||
// The current upstream endpoint returns windows only; do not invent balances.
|
||||
expect(report.balances).toBeUndefined();
|
||||
expect(await readFile(path, "utf8")).toBe(content);
|
||||
|
||||
@@ -65,14 +65,16 @@ export async function fetchUsage(
|
||||
const data = responseSchema.parse(await response.json());
|
||||
const windows = (
|
||||
[
|
||||
["rolling", "Rolling", data.usage.rolling],
|
||||
["weekly", "Weekly", data.usage.weekly],
|
||||
["monthly", "Monthly", data.usage.monthly],
|
||||
// The rolling window's length is not reported, so its percent stands without a name.
|
||||
["rolling", "Rolling", "", data.usage.rolling],
|
||||
["weekly", "Weekly", "wk", data.usage.weekly],
|
||||
["monthly", "Monthly", "mo", data.usage.monthly],
|
||||
] as const
|
||||
).map(([id, label, value]) =>
|
||||
).map(([id, label, shortLabel, value]) =>
|
||||
windowFromUsedPct({
|
||||
id,
|
||||
label,
|
||||
shortLabel,
|
||||
utilizationPct: value.percent,
|
||||
resetsAt: value.resetsAt,
|
||||
tone: toneFromUsedPct(value.percent),
|
||||
|
||||
Reference in New Issue
Block a user