move the quota to the prompt
CI / verify (pull_request) Failing after 8s

This commit is contained in:
2026-09-18 10:08:13 +02:00
parent df61d9a282
commit d39408afc4
2 changed files with 57 additions and 22 deletions
+20 -9
View File
@@ -60,32 +60,43 @@ export default Plugin.define({
setRevision((value) => value + 1); setRevision((value) => value + 1);
}); });
const Slot = (props: { instance: string }) => { const Slot = (props: { instance: string; centered?: boolean; visible?: () => boolean }) => {
const [status] = createResource( const [status] = createResource(
() => `${props.instance}:${revision()}`, () => (props.visible?.() === false ? false : `${props.instance}:${revision()}`),
() => loadQuotaStatus(() => quota.status({})), () => loadQuotaStatus(() => quota.status({})),
); );
return ( const Content = () => (
<Show when={status()}>
<box flexDirection="column"> <box flexDirection="column">
<text fg={context.theme.text.default}> <text fg={context.theme.text.default}>
<b>ChatGPT quota ({status()!.plan})</b> <b>ChatGPT quota ({status()!.plan})</b>
</text> </text>
<For each={status()!.windows}> <For each={status()!.windows}>
{(window) => ( {(window) => (
<text fg={context.theme.text.muted}> <text fg={context.theme.text.muted}>{formatQuotaWindow(window)}</text>
{formatQuotaWindow(window)}
</text>
)} )}
</For> </For>
</box> </box>
);
return (
<Show when={status()}>
<Show when={props.centered} fallback={<Content />}>
<box width="100%" alignItems="center">
<Content />
</box>
</Show>
</Show> </Show>
); );
}; };
const removeHome = context.ui.slot({ const removeHome = context.ui.slot({
append: "home.footer.status", after: "prompt.footer",
render: () => <Slot instance="home" />, render: () => (
<Slot
instance="home"
centered
visible={() => context.ui.router.current().type === "home"}
/>
),
}); });
const removeSidebar = context.ui.slot({ const removeSidebar = context.ui.slot({
append: "sidebar.footer", append: "sidebar.footer",
+30 -6
View File
@@ -1,12 +1,13 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import type { Plugin } from "@opencode/plugin/tui"; import type { Plugin } from "@opencode/plugin/tui";
import type { Route, SlotMap } from "@opencode/plugin/tui/context";
import { testRender, type JSX } from "@opentui/solid"; import { testRender, type JSX } from "@opentui/solid";
import { createSignal } from "solid-js"; import { createSignal } from "solid-js";
import { build } from "../scripts/build.js"; import { build } from "../scripts/build.js";
import type { QuotaStatus } from "../src/quota.js"; import type { QuotaStatus } from "../src/quota.js";
type SidebarSlotRender = (input: { readonly sessionID: string }) => JSX.Element; type SidebarSlotRender = (input: { readonly sessionID: string }) => JSX.Element;
type HomeSlotRender = (input: Record<string, never>) => JSX.Element; type HomeSlotRender = (input: SlotMap["prompt.footer"]) => JSX.Element;
const status = (plan: string, remaining: number): QuotaStatus => ({ const status = (plan: string, remaining: number): QuotaStatus => ({
plan, plan,
@@ -39,6 +40,7 @@ test("built quota slot stays reactive across pending, empty, failure, update, an
let sidebarRender: SidebarSlotRender | undefined; let sidebarRender: SidebarSlotRender | undefined;
let homeRender: HomeSlotRender | undefined; let homeRender: HomeSlotRender | undefined;
let notifyUpdated: (() => void) | undefined; let notifyUpdated: (() => void) | undefined;
const [route, setRoute] = createSignal<Route>({ type: "home" });
const quota = { const quota = {
status() { status() {
@@ -58,11 +60,12 @@ test("built quota slot stays reactive across pending, empty, failure, update, an
client: { rpc: () => quota }, client: { rpc: () => quota },
ui: { ui: {
slot(claim: { slot(claim: {
append: "home.footer.status" | "sidebar.footer"; after?: "prompt.footer";
append?: "sidebar.footer";
render: HomeSlotRender | SidebarSlotRender; render: HomeSlotRender | SidebarSlotRender;
}) { }) {
if (claim.append === "home.footer.status") { if (claim.after === "prompt.footer") {
// SAFETY: The home slot renderer receives the empty home slot input. // SAFETY: The prompt footer renderer receives the matching slot input.
homeRender = claim.render as HomeSlotRender; homeRender = claim.render as HomeSlotRender;
} else { } else {
// SAFETY: The only other fixture path is the session-aware sidebar slot. // SAFETY: The only other fixture path is the session-aware sidebar slot.
@@ -70,6 +73,7 @@ test("built quota slot stays reactive across pending, empty, failure, update, an
} }
return () => undefined; return () => undefined;
}, },
router: { current: route },
}, },
theme: { text: { default: "#ffffff", muted: "#888888" } }, theme: { text: { default: "#ffffff", muted: "#888888" } },
}; };
@@ -81,12 +85,32 @@ test("built quota slot stays reactive across pending, empty, failure, update, an
throw new Error("Plugin did not register its slots"); throw new Error("Plugin did not register its slots");
const renderHome = homeRender; const renderHome = homeRender;
const home = await testRender(() => renderHome({}), { width: 70, height: 5 }); const home = await testRender(() => renderHome({ mode: "normal", showDetails: false }), {
width: 40,
height: 5,
});
await home.waitForFrame((frame) => frame.includes("ChatGPT quota (home)")); await home.waitForFrame((frame) => frame.includes("ChatGPT quota (home)"));
expect(home.captureCharFrame()).toContain("5h: 90% · due"); const homeFrame = home.captureCharFrame();
expect(homeFrame).toContain(" ChatGPT quota (home)");
expect(homeFrame).toContain(" 5h: 90% · due");
expect(statusCalls).toBe(1); expect(statusCalls).toBe(1);
home.renderer.destroy(); home.renderer.destroy();
setRoute({ type: "session", sessionID: "session-one" });
const sessionPrompt = await testRender(
() =>
renderHome({
sessionID: "session-one",
mode: "normal",
showDetails: false,
}),
{ width: 40, height: 5 },
);
await sessionPrompt.renderOnce();
expect(sessionPrompt.captureCharFrame()).not.toContain("ChatGPT quota");
expect(statusCalls).toBe(1);
sessionPrompt.renderer.destroy();
const [sessionID, setSessionID] = createSignal("session-one"); const [sessionID, setSessionID] = createSignal("session-one");
const renderSlot = sidebarRender; const renderSlot = sidebarRender;
const setup = await testRender( const setup = await testRender(