From 663ae7ace1396734a049d9408510fc6ee35e0aba Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Tue, 23 Jun 2026 17:42:49 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20recherche=20worktrees,=20sessions=20exte?= =?UTF-8?q?rnes=20masqu=C3=A9es,=20rendu/historique=20terminal=20&=20plein?= =?UTF-8?q?=20=C3=A9cran=20grille?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Recherche : le watcher débouncé du singleton useWorktreeView est créé dans un effectScope détaché → ne gèle plus après une navigation ; recherche aussi par label de dépôt ; accès à la palette depuis le MoreSheet mobile (sidebar/⌘K indisponibles sur mobile). - Sessions : sessionsForCwd exclut désormais toujours les sessions masquées ; les sessions externes (discovered) sont masquées par défaut côté UI, avec puce de révélation par carte et case globale dans le dashboard. - Terminal : ACK découplé du callback de rendu (armé à la réception + flush au retour de visibilité) → plus de pause PTY quand l'onglet passe en arrière-plan ; repaint forcé sur perte de contexte WebGL / resize / retour en avant-plan. Historique élargi : REPLAY_TAIL_BYTES 1 Mo, RING_CAPACITY 4 Mo, scrollback xterm 20k. - Grille de groupe : bouton « plein écran » sur chaque cellule ouvrant /sessions/:id. --- packages/server/src/core/pty-manager.ts | 4 +- packages/server/src/core/worktree-manager.ts | 16 +++-- packages/server/test/pty-manager.test.ts | 6 +- packages/shared/src/protocol.ts | 8 ++- packages/web/src/components/TerminalCell.vue | 12 +++- packages/web/src/components/TerminalGrid.vue | 16 ++++- packages/web/src/components/TerminalView.vue | 23 +++++++- packages/web/src/components/WorktreeCard.vue | 24 +++++++- .../web/src/components/layout/MoreSheet.vue | 17 ++++++ .../web/src/composables/useWorktreeView.ts | 19 +++++- packages/web/src/i18n/en.ts | 3 + packages/web/src/i18n/fr.ts | 3 + packages/web/src/lib/ws-client.ts | 59 +++++++++++++++---- packages/web/src/stores/worktrees.ts | 4 ++ packages/web/src/views/DashboardView.vue | 8 +++ 15 files changed, 192 insertions(+), 30 deletions(-) diff --git a/packages/server/src/core/pty-manager.ts b/packages/server/src/core/pty-manager.ts index c9f88db..203adee 100644 --- a/packages/server/src/core/pty-manager.ts +++ b/packages/server/src/core/pty-manager.ts @@ -12,7 +12,9 @@ import { SessionActivityTracker } from './claude-adapter.js'; import type { PushService } from './push-service.js'; import type { Db } from '../db/index.js'; -const RING_CAPACITY = 2 * 1024 * 1024; +// 4 Mo : conserve assez d'historique pour que le replay (REPLAY_TAIL_BYTES = 1 Mo) reste largement +// dans le ring et qu'on puisse remonter une conversation Claude après ré-attache. +const RING_CAPACITY = 4 * 1024 * 1024; const KILL_GRACE_MS = 5000; /** Délai avant envoi d'une notif push sur passage en `waiting` : annulé si la session repart (faux positif). */ const NOTIFY_DEBOUNCE_MS = 1500; diff --git a/packages/server/src/core/worktree-manager.ts b/packages/server/src/core/worktree-manager.ts index 2edde00..b3e6ddd 100644 --- a/packages/server/src/core/worktree-manager.ts +++ b/packages/server/src/core/worktree-manager.ts @@ -250,10 +250,18 @@ export class WorktreeManager extends EventEmitter { // ---- worktrees ---- - /** Sessions (managées + découvertes) dont le cwd correspond à ce chemin de worktree. */ - private sessionsForCwd(path: string): SessionSummary[] { + /** + * Sessions (managées + découvertes) dont le cwd correspond à ce chemin de worktree. + * Les sessions explicitement masquées (`hidden`) sont exclues — cohérent avec `/api/v1/sessions` + * (sans quoi le masquage était ignoré dans les fiches worktree). Le tri managées/externes est laissé + * au client (interrupteur « afficher les externes »), qui dispose du champ `source`. La garde de + * suppression réclame en revanche TOUTES les sessions vivantes (`includeHidden`) pour rester sûre. + */ + private sessionsForCwd(path: string, opts?: { includeHidden?: boolean }): SessionSummary[] { const rp = resolve(path); - return mergeSessions(this.ptyManager.list(), this.discovery.list()).filter((s) => resolve(s.cwd) === rp); + return mergeSessions(this.ptyManager.list(), this.discovery.list()) + .filter((s) => resolve(s.cwd) === rp) + .filter((s) => opts?.includeHidden || !s.hidden); } private toSummary(repoId: string, repoPath: string, w: ParsedWorktree, status: WorktreeGitStatus): WorktreeSummary { @@ -507,7 +515,7 @@ export class WorktreeManager extends EventEmitter { if (!w) throw httpError(404, 'NOT_FOUND', 'No such worktree under this repo'); if (resolve(w.path) === resolve(row.path)) throw httpError(400, 'IS_MAIN_WORKTREE', 'Cannot remove the main worktree'); // garde-fou : une session vivante tourne dans ce worktree → exiger une confirmation explicite. - if (!force && this.sessionsForCwd(w.path).some((s) => s.live)) { + if (!force && this.sessionsForCwd(w.path, { includeHidden: true }).some((s) => s.live)) { throw httpError(409, 'SESSION_LIVE_IN_WORKTREE', 'A live session runs in this worktree — pass force to delete anyway'); } return this.withLock(repoId, async () => { diff --git a/packages/server/test/pty-manager.test.ts b/packages/server/test/pty-manager.test.ts index ad5a3da..08b56ba 100644 --- a/packages/server/test/pty-manager.test.ts +++ b/packages/server/test/pty-manager.test.ts @@ -243,7 +243,11 @@ describe('PtyManager (pty mocké)', () => { it('resync à l’attach = queue du ring (REPLAY_TAIL_BYTES max)', () => { const { summary, pty } = spawnBash(); - const chunks = ['A', 'B', 'C'].map((c) => c.repeat(100 * 1024)); + // on écrit volontairement PLUS que REPLAY_TAIL_BYTES (mais < RING_CAPACITY) pour vérifier + // le plafonnement de la queue rejouée. Tailles dérivées de la constante → robuste aux bumps. + const chunkSize = 256 * 1024; + const chunkCount = Math.ceil(REPLAY_TAIL_BYTES / chunkSize) + 2; + const chunks = Array.from({ length: chunkCount }, (_, i) => String.fromCharCode(65 + (i % 26)).repeat(chunkSize)); for (const c of chunks) pty.emitData(c); const b = makeBinding('interactive'); diff --git a/packages/shared/src/protocol.ts b/packages/shared/src/protocol.ts index a3a5820..4c058fb 100644 --- a/packages/shared/src/protocol.ts +++ b/packages/shared/src/protocol.ts @@ -51,8 +51,12 @@ export const FLOW = { LAGGING_BYTES: 2 * 1024 * 1024, } as const; -/** Replay à l'attach : reset terminal + queue du ring (l'écran TUI se repeint en continu) */ -export const REPLAY_TAIL_BYTES = 256 * 1024; +/** + * Replay à l'attach : reset terminal + queue du ring (l'écran TUI se repeint en continu). + * 1 Mo (≈ 10–15k lignes) pour permettre de remonter une vraie conversation Claude dans le terminal ; + * reste < LAGGING_BYTES (pas de faux lagging) et bien dans RING_CAPACITY. + */ +export const REPLAY_TAIL_BYTES = 1024 * 1024; // ---- États de session (sous-ensemble P1 ; étendu en P3) ---- export type SessionRuntimeStatus = diff --git a/packages/web/src/components/TerminalCell.vue b/packages/web/src/components/TerminalCell.vue index f0c58b8..6ea2c07 100644 --- a/packages/web/src/components/TerminalCell.vue +++ b/packages/web/src/components/TerminalCell.vue @@ -12,6 +12,14 @@ {{ t('groups.spanBadge', spannedCount) }} {{ session.command }} +