Skip to content

Commit 0e22c00

Browse files
committed
fix(tui): correct context dialog layout
1 parent 824ff05 commit 0e22c00

3 files changed

Lines changed: 122 additions & 46 deletions

File tree

.grace/verification/main.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<V-M-PLUGIN-HASHLINE-EDIT><TestFiles><File>src/plugins/hashline-edit*.test.ts</File></TestFiles><Checks><Command>bun test src/plugins/hashline-edit*.test.ts</Command><Command>bun x tsc --noEmit</Command></Checks></V-M-PLUGIN-HASHLINE-EDIT>
4545
<V-M-PLUGIN-SYSTEM-CONTEXT-INJECTION><TestFiles><File>src/plugins/system-context-injection.test.ts</File></TestFiles><Checks><Command>bun test src/plugins/system-context-injection.test.ts</Command><Command>bun x tsc --noEmit</Command></Checks></V-M-PLUGIN-SYSTEM-CONTEXT-INJECTION>
4646
<V-M-PLUGIN-SECRETS-REDACTION><TestFiles><File>src/plugins/secrets-redaction/*.test.ts</File></TestFiles><Checks><Command>bun test src/plugins/secrets-redaction/*.test.ts</Command><Command>bun run typecheck</Command><Command>bun run lint</Command></Checks></V-M-PLUGIN-SECRETS-REDACTION>
47-
<V-M-PLUGIN-CONTEXT-TUI><TestFiles><File>src/tui/context/analyze.test.ts</File><File>src/tui/context/plugin.test.ts</File></TestFiles><Checks><Command>bun test src/tui/context/analyze.test.ts src/tui/context/plugin.test.ts</Command><Command>bun run typecheck</Command><Command>bun run build</Command><Command>bun run pack:check</Command></Checks><Expected>Measured provider usage, compaction cutoff, estimated categories, drift, MCP status, command registration, active-session gating, toggle behavior, and bounded failures remain deterministic.</Expected></V-M-PLUGIN-CONTEXT-TUI>
47+
<V-M-PLUGIN-CONTEXT-TUI><TestFiles><File>src/tui/context/analyze.test.ts</File><File>src/tui/context/plugin.test.ts</File><File>src/tui/context/view.test.ts</File></TestFiles><Checks><Command>bun test src/tui/context/analyze.test.ts src/tui/context/plugin.test.ts src/tui/context/view.test.ts</Command><Command>bun run typecheck</Command><Command>bun run build</Command><Command>bun run pack:check</Command></Checks><Expected>Measured provider usage, compaction cutoff, estimated categories, drift, MCP status, command registration, active-session gating, toggle behavior, bounded failures, host-owned dialog composition, and post-replacement xlarge sizing remain deterministic.</Expected></V-M-PLUGIN-CONTEXT-TUI>
4848
<V-M-CLI-ROLE><TestFiles><File>src/commands/role.test.ts</File></TestFiles><Checks><Command>bun test src/commands/role.test.ts</Command><Command>bun run lint</Command><Command>bun run fmt:check</Command></Checks></V-M-CLI-ROLE>
4949
<V-M-CLI-CONFIG-VALIDATE><TestFiles><File>src/commands/config-validate.test.ts</File></TestFiles><Checks><Command>bun run lint</Command><Command>bun run fmt:check</Command><Command>bun run build</Command></Checks></V-M-CLI-CONFIG-VALIDATE>
5050
<V-M-CLI-PRESET><TestFiles><File>src/commands/preset.test.ts</File></TestFiles><Checks><Command>bun test src/commands/preset.test.ts</Command><Command>bun run lint</Command><Command>bun run fmt:check</Command></Checks></V-M-CLI-PRESET>

src/tui/context/view.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// FILE: src/tui/context/view.test.ts
2+
// VERSION: 1.0.0
3+
// START_MODULE_CONTRACT
4+
// PURPOSE: Verify that /context renders inside the host-owned dialog with stable xlarge sizing.
5+
// SCOPE: Renderer registration, post-replacement size selection, and host Dialog ownership regression coverage.
6+
// DEPENDS: [bun:test, @opencode-ai/plugin/tui, src/tui/context/view.tsx]
7+
// LINKS: [M-PLUGIN-CONTEXT-TUI, V-M-PLUGIN-CONTEXT-TUI]
8+
// ROLE: TEST
9+
// MAP_MODE: LOCALS
10+
// END_MODULE_CONTRACT
11+
//
12+
// START_MODULE_MAP
13+
// context dialog composition test - Prevent host and plugin Dialog wrappers from being nested.
14+
// END_MODULE_MAP
15+
//
16+
// START_CHANGE_SUMMARY
17+
// LAST_CHANGE: [DIRECT-FIX - Added regression coverage for host-owned positioning and size reset order.]
18+
// END_CHANGE_SUMMARY
19+
20+
import { expect, test } from "bun:test";
21+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
22+
import type { ContextAnalysis } from "./types.js";
23+
import { openContextDialog } from "./view.js";
24+
25+
test("renders context content directly in the host dialog", () => {
26+
let dialogReads = 0;
27+
let selectedSize: string | undefined;
28+
let render: (() => unknown) | undefined;
29+
const dialogEvents: string[] = [];
30+
31+
const ui = {
32+
get Dialog() {
33+
dialogReads += 1;
34+
return () => null;
35+
},
36+
dialog: {
37+
setSize: (size: string) => {
38+
selectedSize = size;
39+
dialogEvents.push(`size:${size}`);
40+
},
41+
replace: (renderer: () => unknown) => {
42+
render = renderer;
43+
dialogEvents.push("replace");
44+
},
45+
},
46+
};
47+
const api = {
48+
theme: {
49+
current: {
50+
text: "#ffffff",
51+
textMuted: "#888888",
52+
primary: "#00ffff",
53+
warning: "#ffff00",
54+
},
55+
},
56+
ui,
57+
} as unknown as TuiPluginApi;
58+
59+
openContextDialog(api, emptyAnalysis());
60+
61+
expect(selectedSize).toBe("xlarge");
62+
expect(render).toBeFunction();
63+
expect(dialogReads).toBe(0);
64+
expect(dialogEvents).toEqual(["replace", "size:xlarge"]);
65+
});
66+
67+
function emptyAnalysis(): ContextAnalysis {
68+
return {
69+
sessionID: "session-1",
70+
categories: [],
71+
estimatedKnownTokens: 0,
72+
estimatedTotalTokens: 0,
73+
estimationDriftTokens: 0,
74+
compacted: false,
75+
activeMessageCount: 0,
76+
mcpServers: [],
77+
warnings: [],
78+
};
79+
}

src/tui/context/view.tsx

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// VERSION: 1.0.0
33
// START_MODULE_CONTRACT
44
// PURPOSE: Render the context analysis as an OpenCode-native measured-versus-estimated dialog.
5-
// SCOPE: Static xlarge dialog composition, token formatting, usage bar, category rows, MCP status, warnings, and accuracy legend.
5+
// SCOPE: Host-managed xlarge dialog content, token formatting, usage bar, category rows, MCP status, warnings, and accuracy legend.
66
// DEPENDS: [@opencode-ai/plugin/tui, @opentui/core, @opentui/solid, src/tui/context/types.ts]
77
// LINKS: [M-PLUGIN-CONTEXT-TUI, DF-CONTEXT-INSPECTION, V-M-PLUGIN-CONTEXT-TUI]
88
// ROLE: RUNTIME
@@ -14,7 +14,7 @@
1414
// END_MODULE_MAP
1515
//
1616
// START_CHANGE_SUMMARY
17-
// LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added the /context measured and approximate breakdown dialog.]
17+
// LAST_CHANGE: [DIRECT-FIX - Let the host own modal bounds and apply xlarge sizing after stack replacement.]
1818
// END_CHANGE_SUMMARY
1919

2020
import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
@@ -25,58 +25,55 @@ type Color = RGBA | string;
2525

2626
// START_BLOCK_CONTEXT_DIALOG
2727
export function openContextDialog(api: TuiPluginApi, analysis: ContextAnalysis): void {
28-
const Dialog = api.ui.Dialog;
2928
const theme = api.theme.current;
30-
api.ui.dialog.setSize("xlarge");
3129
api.ui.dialog.replace(() => (
32-
<Dialog size="xlarge" onClose={() => api.ui.dialog.clear()}>
33-
<box flexDirection="column" gap={1} paddingLeft={1} paddingRight={1}>
34-
<box flexDirection="row" justifyContent="space-between">
35-
<text fg={theme.text}>Context usage</text>
36-
<text fg={theme.textMuted}>{formatModel(analysis)}</text>
37-
</box>
30+
<box flexDirection="column" gap={1} paddingLeft={1} paddingRight={1}>
31+
<box flexDirection="row" justifyContent="space-between">
32+
<text fg={theme.text}>Context usage</text>
33+
<text fg={theme.textMuted}>{formatModel(analysis)}</text>
34+
</box>
3835

39-
<UsageSummary analysis={analysis} primary={theme.primary} muted={theme.textMuted} />
40-
41-
<box flexDirection="column">
42-
<text fg={theme.text}>Approximate breakdown</text>
43-
{analysis.categories.map((category) => (
44-
<box flexDirection="row" justifyContent="space-between">
45-
<text fg={category.source === "provider-residual" ? theme.warning : theme.textMuted}>
46-
{category.source === "estimated" ? `~ ${category.label}` : category.label}
47-
</text>
48-
<text fg={theme.text}>{formatTokens(category.estimatedTokens)}</text>
49-
</box>
50-
))}
51-
</box>
36+
<UsageSummary analysis={analysis} primary={theme.primary} muted={theme.textMuted} />
37+
38+
<box flexDirection="column">
39+
<text fg={theme.text}>Approximate breakdown</text>
40+
{analysis.categories.map((category) => (
41+
<box flexDirection="row" justifyContent="space-between">
42+
<text fg={category.source === "provider-residual" ? theme.warning : theme.textMuted}>
43+
{category.source === "estimated" ? `~ ${category.label}` : category.label}
44+
</text>
45+
<text fg={theme.text}>{formatTokens(category.estimatedTokens)}</text>
46+
</box>
47+
))}
48+
</box>
5249

53-
<box flexDirection="row" justifyContent="space-between">
54-
<text fg={theme.textMuted}>Active messages</text>
55-
<text fg={theme.text}>
56-
{analysis.activeMessageCount}
57-
{analysis.compacted ? " (after compaction)" : ""}
58-
</text>
59-
</box>
50+
<box flexDirection="row" justifyContent="space-between">
51+
<text fg={theme.textMuted}>Active messages</text>
52+
<text fg={theme.text}>
53+
{analysis.activeMessageCount}
54+
{analysis.compacted ? " (after compaction)" : ""}
55+
</text>
56+
</box>
6057

61-
<McpSummary analysis={analysis} text={theme.text} muted={theme.textMuted} />
58+
<McpSummary analysis={analysis} text={theme.text} muted={theme.textMuted} />
6259

63-
{analysis.estimationDriftTokens > 0 ? (
64-
<text fg={theme.warning}>
65-
Estimate drift: +{formatTokens(analysis.estimationDriftTokens)} above provider usage
66-
</text>
67-
) : null}
60+
{analysis.estimationDriftTokens > 0 ? (
61+
<text fg={theme.warning}>
62+
Estimate drift: +{formatTokens(analysis.estimationDriftTokens)} above provider usage
63+
</text>
64+
) : null}
6865

69-
{analysis.warnings.slice(0, 3).map((warning) => (
70-
<text fg={theme.warning}>Warning: {warning}</text>
71-
))}
66+
{analysis.warnings.slice(0, 3).map((warning) => (
67+
<text fg={theme.warning}>Warning: {warning}</text>
68+
))}
7269

73-
<text fg={theme.textMuted}>
74-
Measured = latest provider usage. ~ = local estimate. MCP tools without source metadata
75-
are grouped.
76-
</text>
77-
</box>
78-
</Dialog>
70+
<text fg={theme.textMuted}>
71+
Measured = latest provider usage. ~ = local estimate. MCP tools without source metadata are
72+
grouped.
73+
</text>
74+
</box>
7975
));
76+
api.ui.dialog.setSize("xlarge");
8077
}
8178
// END_BLOCK_CONTEXT_DIALOG
8279

0 commit comments

Comments
 (0)