Files
arboretum/packages/server/src/core/screen-reader.ts
Johan LEROY fae288f511 P3-C: dashboard worktree-first + acceptance P3 (MVP complet)
Vue racine consolidée : repos → worktrees avec état git ET état de session
corrélé. Complète le MVP (P2 découverte/reprise + P3-A worktrees + P3-B états fins).

- web/lib/ws-client: abonnement multi-topics (sessions + worktrees), subscribeWorktrees.
- web/stores/worktrees: repos + worktrees, CRUD, temps réel (repo_update/worktree_*).
- web/views/DashboardView (route racine /), components RepoSection + WorktreeCard ;
  SessionStateBadge réutilisé pour l'état des sessions corrélées.
- router: / = dashboard, /sessions = liste à plat (sessions hors worktree), nav croisée.
- i18n EN/FR (dashboard/repos/worktrees).
- fix: @xterm/headless est CommonJS → chargé via createRequire (l'import nommé ESM
  échouait sous Node natif, masqué par esbuild en test) ; détecté par l'acceptation.
- scripts/acceptance-p3.mjs: repo git tmp → enregistrement, worktree + hook, broadcast
  WS worktree_update, corrélation session bash, suppression (409 sans force, 200 avec).

Vérifs : typecheck, 159 tests, build (vue-tsc), acceptations P1/P2/P3 ALL GREEN.
2026-06-12 18:46:45 +02:00

46 lines
1.8 KiB
TypeScript

// Reconstruction d'écran via @xterm/headless : terminal headless PERSISTANT par session, alimenté
// incrémentalement par le flux PTY. Remplace le strip ANSI naïf (qui « mange les espaces » et casse
// la détection des dialogues — verdict S1/S3). Aucune dépendance DOM (usage Node).
import { createRequire } from 'node:module';
import type { Terminal as XtermTerminal } from '@xterm/headless';
// @xterm/headless est publié en CommonJS : sous Node ESM natif l'import nommé échoue
// (cjs-module-lexer ne détecte pas l'export). On charge via require, typé par l'import type.
const require = createRequire(import.meta.url);
const { Terminal } = require('@xterm/headless') as { Terminal: typeof XtermTerminal };
export class ScreenReader {
private readonly term: XtermTerminal;
constructor(cols = 120, rows = 40) {
this.term = new Terminal({ cols, rows, scrollback: 0, allowProposedApi: true });
}
/**
* Alimente le terminal avec un chunk PTY brut (la frontière UTF-8 et les séquences ANSI coupées
* sont gérées par xterm). La promesse se résout quand le chunk a été parsé (snapshot fiable ensuite).
*/
feed(chunk: Uint8Array | string): Promise<void> {
return new Promise((resolve) => this.term.write(chunk as Uint8Array, resolve));
}
resize(cols: number, rows: number): void {
this.term.resize(cols, rows);
}
/** Lignes visibles du viewport courant, espaces préservés (trim à droite uniquement). */
snapshotLines(): string[] {
const buf = this.term.buffer.active;
const lines: string[] = [];
for (let y = 0; y < this.term.rows; y++) {
const line = buf.getLine(buf.baseY + y);
lines.push(line ? line.translateToString(true) : '');
}
return lines;
}
dispose(): void {
this.term.dispose();
}
}