Skip to content

Commit aa7a039

Browse files
committed
Pin the TUI header and footer (#4)
1 parent c341981 commit aa7a039

2 files changed

Lines changed: 43 additions & 15 deletions

File tree

‎src/tui/app.tsx‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,25 @@ export function App({ eventEmitter, agent, sessionTitle }: AppProps): ReactNode
3333
};
3434

3535
return (
36-
<Box flexDirection="column" height="100%">
37-
<Header
38-
turnsUsed={state.turnsUsed}
39-
status={state.status}
40-
totalCost={state.formattedCost}
41-
sessionTitle={sessionTitle}
42-
latestUserMessage={state.latestUserMessage}
43-
/>
44-
<Box flexGrow={1} flexDirection="column">
36+
<Box flexDirection="column" height="100%" overflowY="hidden">
37+
<Box height="auto" flexShrink={0}>
38+
<Header
39+
turnsUsed={state.turnsUsed}
40+
status={state.status}
41+
totalCost={state.formattedCost}
42+
sessionTitle={sessionTitle}
43+
latestUserMessage={state.latestUserMessage}
44+
/>
45+
</Box>
46+
<Box flexGrow={1} flexShrink={1} flexDirection="column" overflowY="hidden">
4547
<EventLog contentBlocks={state.contentBlocks} />
4648
</Box>
47-
<ChatInput onSubmit={handleSend} />
48-
<StatusBar />
49+
<Box height="auto" flexShrink={0}>
50+
<ChatInput onSubmit={handleSend} />
51+
</Box>
52+
<Box height="auto" flexShrink={0}>
53+
<StatusBar />
54+
</Box>
4955
</Box>
5056
);
5157
}

‎tests/unit/tui/app.test.tsx‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ const mockAgent = {
1212

1313
test("App renders header and status bar", () => {
1414
const emitter = new EventEmitter();
15-
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} />);
15+
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} sessionTitle="" />);
1616
expect(lastFrame()).toContain("interchange-code");
1717
expect(lastFrame()).toContain("Ctrl+C");
1818
});
1919

2020
test("App renders chat input", () => {
2121
const emitter = new EventEmitter();
22-
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} />);
22+
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} sessionTitle="" />);
2323
expect(lastFrame()).toContain("> ");
2424
});
2525

2626
test("App renders events after they are emitted", async () => {
2727
const emitter = new EventEmitter();
28-
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} />);
28+
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} sessionTitle="" />);
2929

3030
const event: ReactorEmittedEvent = {
3131
type: "inference.tool_call.start",
@@ -40,6 +40,28 @@ test("App renders events after they are emitted", async () => {
4040

4141
test("App renders running status initially", () => {
4242
const emitter = new EventEmitter();
43-
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} />);
43+
const { lastFrame } = render(<App eventEmitter={emitter} maxTurns={30} agent={mockAgent as unknown as import("@intx/agent").Agent} sessionTitle="" />);
4444
expect(lastFrame()).toContain("running");
4545
});
46+
47+
test("App keeps header and footer visible after many events", async () => {
48+
const emitter = new EventEmitter();
49+
const { lastFrame } = render(
50+
<App eventEmitter={emitter} agent={mockAgent as unknown as import("@intx/agent").Agent} sessionTitle="scroll test" />,
51+
{ stdout: { columns: 100, rows: 12 } },
52+
);
53+
54+
for (let i = 0; i < 25; i++) {
55+
emitter.emit("event", {
56+
type: "inference.tool_call.start",
57+
seq: i + 1,
58+
data: { name: `tool_${i}`, callId: `call_${i}` } as unknown as ReactorEmittedEvent["data"],
59+
} satisfies ReactorEmittedEvent);
60+
}
61+
62+
await new Promise((resolve) => setTimeout(resolve, 10));
63+
expect(lastFrame()).toContain("interchange-code");
64+
expect(lastFrame()).toContain("scroll test");
65+
expect(lastFrame()).toContain("> ");
66+
expect(lastFrame()).toContain("Ctrl+C");
67+
});

0 commit comments

Comments
 (0)