test: drive the phase-2 verbs end to end under a pty

The fake API server grows the routes the dashboard now calls (terminal tail,
input, approvals answer, search, away digest, plan usage on status), and the
new cases assert on what the server RECEIVED rather than on the frame: the
prompt arrives as one line ending in a carriage return, and the answers as
the exact action and option digit.

Also covered: the tail refreshing in place, the search overlay selecting a
live session, the digest rendering, one bell for an item announced twice,
and the 409 path reported as "no longer on screen".

The plan-usage chip is punctuated with the glyph tier's separator, so an
ASCII terminal no longer gets a stray middle dot in the header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-22 14:13:58 +02:00
parent eb8958ddd0
commit b51abe2c27
4 changed files with 348 additions and 14 deletions
+9 -3
View File
@@ -58,6 +58,7 @@ import {
TuiClient,
type TuiApprovalAnswer,
type TuiEventStream,
type TuiPlanUsage,
type TuiQuickStartOptions,
type TuiTmuxSession,
} from './tui-client.js';
@@ -664,7 +665,7 @@ class TuiApp {
...(server.hostname ? { hostname: server.hostname } : {}),
...(server.instance ? { instance: server.instance } : {}),
...(server.version ? { version: server.version } : {}),
...(server.planUsage ? { planUsage: formatPlanUsage(server.planUsage) } : {}),
...(server.planUsage ? { planUsage: this.planUsageChip(server.planUsage) } : {}),
});
} else {
this.model.setConnection('degraded');
@@ -689,13 +690,18 @@ class TuiApp {
});
}
/** The chip, punctuated with the glyph tier's own separator. */
private planUsageChip(usage: TuiPlanUsage): string {
return formatPlanUsage(usage, ` ${this.glyphs.separator} `);
}
// ── Data ───────────────────────────────────────────────────────────────────
private subscribe(): void {
this.stream = this.client.subscribeEvents({
onInit: (state) => {
if (state.version) this.model.setHeader({ version: state.version });
if (state.planUsage) this.model.setHeader({ planUsage: formatPlanUsage(state.planUsage) });
if (state.planUsage) this.model.setHeader({ planUsage: this.planUsageChip(state.planUsage) });
this.paint();
},
onResync: () => this.scheduleRefresh(),
@@ -704,7 +710,7 @@ class TuiApp {
// changes which group its row is in), and one code path cannot double-ring.
onApproval: () => this.scheduleRefresh(),
onPlanUsage: (usage) => {
this.model.setHeader({ planUsage: formatPlanUsage(usage) });
this.model.setHeader({ planUsage: this.planUsageChip(usage) });
this.paint();
},
onStatus: (status, detail) => {
+4 -3
View File
@@ -233,9 +233,10 @@ export function formatTokens(total: number): string {
* The header's plan-usage chip: `5h 32% · wk 61%`, the same two windows the web
* chip shows (the statusline telemetry carries no others). Empty when the
* account reports neither, so the header shows no placeholder for a fact that
* does not exist.
* does not exist. The separator is passed in because the header's own comes
* from the glyph tier, and an ASCII terminal must not get a stray `·`.
*/
export function formatPlanUsage(usage: StatusTelemetry | null | undefined): string {
export function formatPlanUsage(usage: StatusTelemetry | null | undefined, separator = ' · '): string {
if (!usage) return '';
const parts: string[] = [];
if (typeof usage.fiveHour?.usedPercentage === 'number') {
@@ -244,7 +245,7 @@ export function formatPlanUsage(usage: StatusTelemetry | null | undefined): stri
if (typeof usage.sevenDay?.usedPercentage === 'number') {
parts.push(`wk ${Math.round(usage.sevenDay.usedPercentage)}%`);
}
return parts.join(' · ');
return parts.join(separator);
}
/**