mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 22:49:41 +02:00
The store gains the three overlays phase 2 needs, each taking the keyboard when it is set and all of them cleared together by closeOverlay(), plus the pure flattening of `GET /api/search`'s typed groups into rows a cursor can move over: headers are chrome, and only a session that is on the list counts as selectable, since a history hit has no row to move the cursor to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
211 lines
7.2 KiB
TypeScript
211 lines
7.2 KiB
TypeScript
/**
|
|
* @fileoverview Shared types for the `codeman tui` pure core.
|
|
*
|
|
* The TUI is a client of the server, never a second brain: its rows are the
|
|
* rows `GET /api/sessions/unified` already returns (`UnifiedSessionItem`) and
|
|
* its blocked states are the items `GET /api/approvals` already parsed
|
|
* (`ApprovalItem`). Both are imported as TYPES only, so nothing here pulls the
|
|
* server, node-pty or the utils barrel into a CLI process.
|
|
*
|
|
* Everything in `src/tui/*` except `tui-app.ts` / `tui-client.ts` is pure:
|
|
* deterministic outputs from inputs, no `process.*`, no timers, no IO.
|
|
*
|
|
* @module tui/tui-types
|
|
*/
|
|
|
|
import type { UnifiedSessionItem } from '../services/unified-session-service.js';
|
|
import type { ApprovalItem } from '../web/approval-inbox.js';
|
|
import type { TuiComposerState } from './tui-composer.js';
|
|
|
|
/**
|
|
* A unified-list row plus the few live-only extras the dashboard shows.
|
|
*
|
|
* The unified list is the spine (it is the only source that carries history
|
|
* rows), but it has no token counters and no turn-start stamp, so the client
|
|
* merges those from the live session payload (`GET /api/sessions` /
|
|
* `session_updated` SSE) when a row is live. History rows simply lack them.
|
|
*/
|
|
export interface TuiSessionRow extends UnifiedSessionItem {
|
|
/**
|
|
* Wall-clock ms of the pane's last Enter (`SessionState.lastSubmitAt`). The
|
|
* only usable "working since" anchor: a working pane repaints about once a
|
|
* second, so its `lastActivityAt` is always "now".
|
|
*/
|
|
lastSubmitAt?: number;
|
|
inputTokens?: number;
|
|
outputTokens?: number;
|
|
/**
|
|
* tmux session name to attach to (`codeman-<first 8 of the id>`).
|
|
*
|
|
* The unified list does not carry it (no server view merges the mux name into
|
|
* a row), so the app layer fills it in from the local tmux enumeration, which
|
|
* is also the only thing that proves the pane really exists. A row without one
|
|
* cannot be attached: it is either history or a direct-PTY session.
|
|
*/
|
|
muxName?: string;
|
|
}
|
|
|
|
/**
|
|
* Row state, in the web UI's vocabulary so both surfaces read the same.
|
|
*
|
|
* There is deliberately no `error` member: an errored session is something a
|
|
* human has to look at, so it classifies as `waiting` and lands in NEEDS YOU
|
|
* rather than growing a fifth color nobody designed.
|
|
*/
|
|
export type TuiSessionState = 'blocked-question' | 'blocked-permission' | 'waiting' | 'working' | 'idle' | 'recent';
|
|
|
|
/** The four display groups, in display order. */
|
|
export type TuiGroupKey = 'needs-you' | 'working' | 'idle' | 'recent';
|
|
|
|
/** A classified session: what the cursor moves over and the renderer paints. */
|
|
export interface TuiRow {
|
|
session: TuiSessionRow;
|
|
state: TuiSessionState;
|
|
group: TuiGroupKey;
|
|
/** The pending prompt that blocks this session, when it has one. */
|
|
approval?: ApprovalItem;
|
|
/** Epoch ms the session entered `state`; the intra-group sort key. 0 when unknown. */
|
|
since: number;
|
|
}
|
|
|
|
export interface TuiGroup {
|
|
key: TuiGroupKey;
|
|
label: string;
|
|
rows: TuiRow[];
|
|
}
|
|
|
|
/** How the client currently sees the server. */
|
|
export type TuiConnectionStatus = 'connected' | 'reconnecting' | 'degraded' | 'down';
|
|
|
|
/** Which overlay (if any) owns the keyboard. */
|
|
export type TuiUiMode = 'list' | 'help' | 'confirm-kill' | 'prompt' | 'search' | 'digest' | 'message' | 'new-session';
|
|
|
|
/**
|
|
* Glyph capability tier. Detection is env-driven and therefore lives in a tiny
|
|
* function the app layer calls (`detectGlyphTier`); the renderer only ever
|
|
* takes the resolved tier as an input.
|
|
*/
|
|
export type TuiGlyphTier = 'nerd' | 'unicode' | 'ascii';
|
|
|
|
/** Header facts, all optional: the header degrades to just the product name. */
|
|
export interface TuiHeaderInfo {
|
|
hostname?: string;
|
|
instance?: string;
|
|
version?: string;
|
|
/** Plan-usage chip text, e.g. `5h 32% · wk 61%`. */
|
|
planUsage?: string;
|
|
}
|
|
|
|
/** The selected session's terminal tail, already run through `toDisplayLines()`. */
|
|
export interface TuiPreview {
|
|
sessionId: string;
|
|
/** Display lines, oldest first. */
|
|
lines: string[];
|
|
/** Set instead of lines when the tail could not be fetched. */
|
|
error?: string;
|
|
/**
|
|
* Set instead of lines when there is nothing to fetch (a history row has no
|
|
* live buffer). Distinct from `error`: nothing failed, so it must not read
|
|
* like something did.
|
|
*/
|
|
note?: string;
|
|
}
|
|
|
|
export interface TuiMessage {
|
|
text: string;
|
|
tone: 'info' | 'warn' | 'err';
|
|
}
|
|
|
|
/** Typed-confirmation state for `x` (kill): the user retypes the session name. */
|
|
export interface TuiConfirmState {
|
|
sessionId: string;
|
|
name: string;
|
|
typed: string;
|
|
}
|
|
|
|
export interface TuiPickerItem {
|
|
/** What choosing this item means to the caller; never shown. */
|
|
id: string;
|
|
label: string;
|
|
/** Second column, dimmed (a case path, a mode description). */
|
|
detail?: string;
|
|
}
|
|
|
|
/**
|
|
* A one-column chooser drawn as an overlay (the case and mode pickers behind
|
|
* `n`). Items are already filtered: the app owns the unfiltered list, the
|
|
* renderer only paints what it is given.
|
|
*/
|
|
export interface TuiPickerState {
|
|
title: string;
|
|
items: TuiPickerItem[];
|
|
/** Index into `items`; -1 when the list is empty. */
|
|
index: number;
|
|
/** Current filter text, when the picker filters as you type. */
|
|
filter?: string;
|
|
/** One line above the list: what is being chosen, or why the list is empty. */
|
|
hint?: string;
|
|
}
|
|
|
|
/** The `p` composer: one line aimed at one session. */
|
|
export interface TuiPromptState {
|
|
sessionId: string;
|
|
/** What the session is called on screen, for the footer prefix. */
|
|
label: string;
|
|
composer: TuiComposerState;
|
|
}
|
|
|
|
/**
|
|
* One line of the `/` overlay. Group headers are chrome (the API returns typed
|
|
* groups), so only `result` rows are selectable.
|
|
*/
|
|
export interface TuiSearchEntry {
|
|
kind: 'header' | 'result';
|
|
text: string;
|
|
detail?: string;
|
|
sessionId?: string;
|
|
/** The row can hand the dashboard a session that is open right now. */
|
|
live?: boolean;
|
|
}
|
|
|
|
export interface TuiSearchState {
|
|
composer: TuiComposerState;
|
|
/** The query `entries` answer. Lags the composer while a search is in flight. */
|
|
query: string;
|
|
entries: TuiSearchEntry[];
|
|
/** Index into `entries`, always a `result` row; -1 when none is selectable. */
|
|
index: number;
|
|
status: 'idle' | 'searching' | 'done' | 'error';
|
|
/** One line under the query: what happened, or why there is nothing. */
|
|
note?: string;
|
|
}
|
|
|
|
/** The `g` overlay: pre-formatted lines plus where the window starts. */
|
|
export interface TuiDigestState {
|
|
title: string;
|
|
lines: string[];
|
|
offset: number;
|
|
}
|
|
|
|
/**
|
|
* What `renderFrame()` reads. The store implements it; a test can hand-build
|
|
* one, which is what keeps the renderer testable without the model.
|
|
*/
|
|
export interface TuiRenderModel {
|
|
groups(): TuiGroup[];
|
|
readonly selectedId: string | null;
|
|
readonly connection: TuiConnectionStatus;
|
|
readonly mode: TuiUiMode;
|
|
readonly header: TuiHeaderInfo;
|
|
readonly preview: TuiPreview | null;
|
|
readonly message: TuiMessage | null;
|
|
readonly confirm: TuiConfirmState | null;
|
|
/** Optional so a test can hand-build a model without one. */
|
|
readonly picker?: TuiPickerState | null;
|
|
readonly prompt?: TuiPromptState | null;
|
|
readonly search?: TuiSearchState | null;
|
|
readonly digest?: TuiDigestState | null;
|
|
/** Live sessions only (RECENT rows are history, not sessions you have open). */
|
|
readonly sessionCount: number;
|
|
}
|