anvilsign in

collin/strudel-claude

main / node-tui / ui / LogPane.ts
1import { Box, Text } from 'ink';
2import { memo } from 'react';
3import type { ReactElement } from 'react';
4import { html } from './html.ts';
5import type { LogEntry } from '../browser-shim.ts';
6
7/** A log line plus the identity the list needs to render it stably. */
8export interface LogRecord extends LogEntry {
9 id: number;
10 /** How many times this message repeated in a row. Absent means once. */
11 count?: number;
12}
13
14// superdough and @strudel/core both prefix their messages with a bracketed
15// origin ("[sampler] load sound ..."). Dimming the prefix keeps the origin
16// available without letting it dominate a pane that's mostly sample loading.
17const PREFIX = /^(\[[^\]]+\])\s*([\s\S]*)$/;
18
19const COLORS: Record<string, string> = { error: 'red', warning: 'yellow' };
20
21function LogLine({ entry }: { entry: LogRecord }): ReactElement {
22 const match = PREFIX.exec(entry.message);
23 const color = entry.type ? COLORS[entry.type] : undefined;
24 const repeat = entry.count && entry.count > 1
25 ? html`<${Text} dimColor> ×${entry.count}<//>`
26 : null;
27
28 if (!match) {
29 return html`
30 <${Text} wrap="truncate-end">
31 <${Text} color=${color}>${entry.message}<//>${repeat}
32 <//>
33 `;
34 }
35
36 return html`
37 <${Text} wrap="truncate-end">
38 <${Text} dimColor>${match[1]} <//>
39 <${Text} color=${color}>${match[2]}<//>${repeat}
40 <//>
41 `;
42}
43
44// Memoised because the app re-renders on every animation frame while the log
45// itself changes only when something is logged. Ink still repaints the frame,
46// but there is no reason to rebuild this subtree twenty times a second.
47export const LogPane = memo(function LogPane({ entries, height }: {
48 entries: readonly LogRecord[];
49 height: number;
50}): ReactElement {
51 const shown = entries.slice(-height);
52 return html`
53 <${Box} flexDirection="column" height=${height}>
54 ${shown.map((entry) => html`<${LogLine} key=${entry.id} entry=${entry} />`)}
55 <//>
56 `;
57});