Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/expand-display-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"oc-usage-limits-plugin": minor
---

Add typed master display, sidebar window filtering, and per-provider footer window selection configuration.
5 changes: 5 additions & 0 deletions .changeset/provider-display-bars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"oc-usage-limits-plugin": minor
---

Move display visibility and window selection to typed provider-level configuration fields.
5 changes: 5 additions & 0 deletions .changeset/separate-visibility-toggles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"oc-usage-limits-plugin": minor
---

Add separate `showSidebar` and `showFooter` configuration toggles for the v2 TUI displays.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ Create `~/.config/opencode/usage-limits.jsonc`. The same file lives at [`example
"codex": {
"enabled": true,
"label": "Codex",
"showSidebarBar": true,
"showFooterBar": true,
"sidebarWindow": "all",
"footerWindow": "auto",
},
"zai": {
"enabled": true,
Expand Down Expand Up @@ -169,6 +173,10 @@ Disabled providers are hidden:
}
```

Top-level `enabled` is the plugin master switch, and `showErrors` controls error text globally. Each provider's `enabled` controls fetching. Provider `showSidebarBar` and `showFooterBar` independently control its sidebar and footer displays without stopping refreshes; both default to `true`.

Each provider's `sidebarWindow` can be `all`, `rolling`, `daily`, `weekly`, `monthly`, `credits`, or `other`. Rolling includes legacy `5h` labels. Each provider accepts `footerWindow` with `auto` (the provider's normal selection), or one of the same window kinds. An unavailable requested footer window falls back to the provider's automatic selection and then its first available window.

## Providers

| Provider ID | Service | Env var | Auth header | Default base URL |
Expand Down
45 changes: 43 additions & 2 deletions __tests__/components.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { testRender } from "@opentui/solid";
import { Result } from "effect";

import { CompactStatusLine, UsageLimitsPanel } from "@/components.tsx";
import type { ProviderState, ProviderUsage, UsageWindow } from "@/types.ts";
import type {
ProviderDisplayConfig,
ProviderState,
ProviderUsage,
UsageWindow,
} from "@/types.ts";
import { parseUsagePercentage, percentageQuota } from "@/usage.ts";

const color = RGBA.fromValues(1, 2, 3, 255);
Expand Down Expand Up @@ -42,7 +47,10 @@ const usage = (overrides: Partial<ProviderUsage> = {}): ProviderUsage => ({
const renderPanelText = async (
states: ProviderState[],
showErrors: boolean,
lastRefreshAt: Date | null = null
lastRefreshAt: Date | null = null,
providerDisplays: Readonly<
Partial<Record<ProviderState["id"], ProviderDisplayConfig>>
> = {}
): Promise<string> => {
const setup = await testRender(
() => (
Expand All @@ -51,6 +59,7 @@ const renderPanelText = async (
states={states}
theme={theme}
lastRefreshAt={lastRefreshAt}
providerDisplays={providerDisplays}
/>
),
{ height: 12, width: 80 }
Expand Down Expand Up @@ -86,6 +95,38 @@ describe("UsageLimitsPanel", () => {
expect(text).toContain("[█████░░░░░░░]");
});

test("filters windows by the provider sidebar window", async () => {
const text = await renderPanelText(
[
{
data: usage({
windows: [
usageWindow(),
usageWindow({ kind: "weekly", label: "weekly" }),
],
}),
id: "codex",
label: "Codex",
stale: false,
status: "ready",
},
],
true,
null,
{
codex: {
footerWindow: "auto",
showFooterBar: true,
showSidebarBar: true,
sidebarWindow: "weekly",
},
}
);

expect(text).toContain("weekly");
expect(text).not.toContain("5h");
});

test("renders previous windows and error text when errors are visible", async () => {
const text = await renderPanelText(
[
Expand Down
8 changes: 8 additions & 0 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ describe("configuration parsing", () => {
authorizationScheme: "bearer",
baseUrl: "https://example.com",
enabled: true,
footerWindow: "weekly",
label: "Work",
showFooterBar: false,
showSidebarBar: true,
sidebarWindow: "weekly",
},
},
});
Expand All @@ -68,7 +72,11 @@ describe("configuration parsing", () => {
authorizationScheme: "bearer",
baseUrl: "https://example.com",
enabled: true,
footerWindow: "weekly",
label: "Work",
showFooterBar: false,
showSidebarBar: true,
sidebarWindow: "weekly",
});
}
});
Expand Down
43 changes: 43 additions & 0 deletions __tests__/plugin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,49 @@ describe("usage-limits TUI lifecycle", () => {
);
});

test("hides both displays without stopping provider refreshes", async () => {
const harness = createHarness(
config({
providers: {
codex: {
enabled: true,
showFooterBar: false,
showSidebarBar: false,
},
},
})
);
const registered = await initialize(harness);

expect(harness.fetches).toEqual(["codex"]);
expect(await renderSlot(registered, "sidebar.content")).not.toContain(
"Usage Limits"
);
expect(await renderSlot(registered, "prompt.footer.status")).not.toContain(
"42%"
);
});

test.each([
[
{ providers: { codex: { enabled: true, showSidebarBar: false } } },
"sidebar.content",
"Usage Limits",
],
[
{ providers: { codex: { enabled: true, showFooterBar: false } } },
"prompt.footer.status",
"42%",
],
])("hides the configured %s display", async (overrides, slot, text) => {
const harness = createHarness(config(overrides));
const registered = await initialize(harness);

expect(
await renderSlot(registered, slot as keyof CharacterizedSlots)
).not.toContain(text);
});

test("does not render footer usage for shell mode or missing sessions", async () => {
const harness = createHarness();
const registered = await initialize(harness);
Expand Down
38 changes: 38 additions & 0 deletions __tests__/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,44 @@ describe("session helpers", () => {
expect(Number(usage ? quotaUsedPercent(usage.quota) : null)).toBe(88);
});

test("selects a requested footer window and falls back to auto", () => {
const states: ProviderState[] = [
{
data: {
capturedAt: new Date(),
id: "codex",
label: "Codex",
windows: [window("5h", 75), window("weekly", 88)],
},
id: "codex",
label: "Codex",
stale: false,
status: "ready",
},
];

expect(
usageForProvider(states, "openai", {
codex: {
footerWindow: "weekly",
showFooterBar: true,
showSidebarBar: true,
sidebarWindow: "all",
},
})?.label
).toBe("weekly");
expect(
usageForProvider(states, "openai", {
codex: {
footerWindow: "monthly",
showFooterBar: true,
showSidebarBar: true,
sidebarWindow: "all",
},
})?.label
).toBe("5h");
});

test("returns null for unknown providers or unavailable data", () => {
expect(usageForProvider([], "anthropic")).toBeNull();
expect(
Expand Down
4 changes: 4 additions & 0 deletions examples/usage-limits.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"codex": {
"enabled": true,
"label": "Codex",
"showSidebarBar": true,
"showFooterBar": true,
"sidebarWindow": "all",
"footerWindow": "auto",
},
"zai": {
"enabled": true,
Expand Down
52 changes: 46 additions & 6 deletions src/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
windowResetText,
windowResetTime,
} from "@/format.ts";
import type { ProviderState, UsageWindow } from "@/types.ts";
import type {
ProviderDisplayConfig,
ProviderState,
SidebarWindow,
UsageWindow,
} from "@/types.ts";
import { quotaUsedPercent } from "@/usage.ts";

/**
Expand Down Expand Up @@ -160,12 +165,47 @@ export const UsageLimitsPanel = (props: {
showErrors: boolean;
theme: UsageTheme;
lastRefreshAt: Date | null;
providerDisplays: Readonly<
Partial<Record<ProviderState["id"], ProviderDisplayConfig>>
>;
}) => {
const colors = resolveTheme(props.theme);
const displayConfigFor = (state: ProviderState): ProviderDisplayConfig =>
props.providerDisplays[state.id] ?? {
footerWindow: "auto",
showFooterBar: true,
showSidebarBar: true,
sidebarWindow: "all",
};
const filteredWindowsFor = (
state: ProviderState,
windows: readonly UsageWindow[]
): UsageWindow[] => {
const sidebarWindow: SidebarWindow = displayConfigFor(state).sidebarWindow;
return sidebarWindow === "all"
? [...windows]
: windows.filter(
(window) =>
window.kind === sidebarWindow ||
(sidebarWindow === "rolling" && window.label === "5h")
);
};
const visibleStates = createMemo(() =>
props.states.filter((state) =>
shouldRenderProviderState(state, props.showErrors)
)
props.states.filter((state) => {
if (!shouldRenderProviderState(state, props.showErrors)) {
return false;
}
if (!displayConfigFor(state).showSidebarBar) {
return false;
}
if (state.status === "ready") {
return filteredWindowsFor(state, state.data.windows).length > 0;
}
if (state.status === "error" && state.previous) {
return filteredWindowsFor(state, state.previous.windows).length > 0;
}
return true;
})
);

return (
Expand Down Expand Up @@ -210,13 +250,13 @@ export const UsageLimitsPanel = (props: {
{state.status === "ready" ? (
<UsageWindowRows
theme={colors}
windows={state.data.windows}
windows={filteredWindowsFor(state, state.data.windows)}
/>
) : null}
{state.status === "error" && state.previous ? (
<UsageWindowRows
theme={colors}
windows={state.previous.windows}
windows={filteredWindowsFor(state, state.previous.windows)}
/>
) : null}
{state.status === "error" && props.showErrors ? (
Expand Down
26 changes: 26 additions & 0 deletions src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,33 @@ const secret = Schema.RedactedFromValue(Schema.String, {

const commonProviderFields = {
enabled: Schema.optionalKey(Schema.Boolean),
footerWindow: defaultKey(
Schema.Literals([
"auto",
"rolling",
"daily",
"weekly",
"monthly",
"credits",
"other",
]),
"auto"
),
label: Schema.optionalKey(Schema.String),
showFooterBar: defaultKey(Schema.Boolean, true),
showSidebarBar: defaultKey(Schema.Boolean, true),
sidebarWindow: defaultKey(
Schema.Literals([
"all",
"rolling",
"daily",
"weekly",
"monthly",
"credits",
"other",
]),
"all"
),
};

/** Schema for Codex provider configuration. */
Expand Down
Loading