refactor(ui): supprime le popup de reprise par cellule (#177)

Retire la surface utilisateur de reprise par cellule :
- supprime ResumeConversationPopup et son test (resumeConversationPopup.test.tsx)
- débranche l'appel dans LayoutGrid et retire l'export terminals/index
- ajuste les tests dépendants (libellé « Terminé » dans LayoutGrid.chat.test, refitEpoch)
- commentaire affiné dans ResumeProjectPanel

La reprise implicite éventuelle sera rediscutée ultérieurement (objectif immédiat : retirer la surface).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 12:24:57 +02:00
parent 30442d05fe
commit 215bc16f21
7 changed files with 4 additions and 562 deletions

View File

@ -22,16 +22,11 @@ import type { Agent, AgentProfile } from "@/domain";
import type { LayoutNode } from "@/domain";
import type { ProjectWorkState } from "@/domain";
import type {
ConversationDetails,
LiveAgent,
OpenTerminalOptions,
TerminalHandle,
} from "@/ports";
import {
ResumeConversationPopup,
TerminalView,
useWritePortal,
} from "@/features/terminals";
import { TerminalView, useWritePortal } from "@/features/terminals";
import { useGateways } from "@/app/di";
import { useProjectWorkState } from "@/features/workstate/useProjectWorkState";
import {
@ -192,7 +187,6 @@ function NodeView({
session={node.node.session ?? null}
agent={node.node.agent ?? null}
conversationId={node.node.conversationId ?? null}
agentWasRunning={node.node.agentWasRunning ?? false}
cwd={cwd}
vm={vm}
parentSplit={parentSplit}
@ -238,7 +232,6 @@ interface LeafViewProps {
session: string | null;
agent: string | null;
conversationId: string | null;
agentWasRunning: boolean;
cwd: string;
vm: LayoutViewModel;
parentSplit: { container: string; index: number; siblings: number } | null;
@ -248,19 +241,6 @@ interface LeafViewProps {
refitSignal: number;
}
/**
* A launch deferred by the resume popup (T7): TerminalView asked the opener to
* launch (fresh open or reattach-failed fallback) for a cell that carries a
* `conversationId`. We hold the open request here until the user picks
* Reprendre / Nouvelle conversation, then resolve the promise with the handle.
*/
interface PendingResume {
opts: OpenTerminalOptions;
onData: (bytes: Uint8Array) => void;
resolve: (handle: TerminalHandle) => void;
reject: (e: unknown) => void;
}
/**
* A transient in-cell notice. `goToNodeId`, when present, identifies the layout
* leaf that already hosts the conflicting live agent; the notice then renders an
@ -355,7 +335,6 @@ function LeafView({
session,
agent,
conversationId,
agentWasRunning,
cwd,
vm,
parentSplit,
@ -812,15 +791,6 @@ function LeafView({
// we never silently relaunch a clone (product rule "1 agent = 1 employé").
const [busyNotice, setBusyNotice] = useState<CellNotice | null>(null);
// ── Resume popup state (T7) ───────────────────────────────────────────────
// When an agent cell carries a persisted conversation id and its PTY session
// is dead, the opener is about to relaunch in Resume mode. We intercept that
// launch with a popup so the user can choose Resume vs a fresh conversation.
// The reattach path (live PTY) never goes through this opener, so the popup is
// naturally skipped there.
const [pendingResume, setPendingResume] = useState<PendingResume | null>(null);
const [resumeDetails, setResumeDetails] = useState<ConversationDetails | null>(null);
// ── Live re-attach on background wake (inter-agent delegation) ─────────────
// Bumped to re-key the TerminalView and force a re-attach to the cell's
// pinned agent when that agent becomes live in the *background* AFTER this
@ -836,8 +806,6 @@ function LeafView({
const [attachGen, setAttachGen] = useState(0);
useEffect(() => {
if (!agentId || !agentGateway?.attachLiveAgent) return;
// Mid resume decision: let the popup own the (re)launch — don't race it.
if (pendingResume) return;
// Only act when the pinned agent is live in ANOTHER (non-visible) cell and
// this cell is not already bound to that session. `backgroundLive` is the
// same guard `doLaunch` and the dropdown use; crucially it ignores a session
@ -913,52 +881,9 @@ function LeafView({
const terminalOpener = agentGateway && agentId
? (opts: OpenTerminalOptions, onData: (bytes: Uint8Array) => void): Promise<TerminalHandle> => {
// No persisted conversation ⇒ fresh cell: open straight away (the launch
// may assign a new id). No popup on this path.
if (!conversationId) {
return doLaunch(opts, onData, undefined);
}
// If the cell was closed/remounted while the agent was running, resume
// silently. Layout/project changes must not interrupt an ongoing turn
// with a modal choice; the persisted conversation id is the continuity
// signal and `agentWasRunning` tells us this was not an intentional stop.
if (agentWasRunning) {
return doLaunch(opts, onData, conversationId);
}
// Resume case: defer the launch behind the popup. Fetch the best-effort
// enriched details (last topic + tokens) to enrich it; failure or empty
// ⇒ degraded mode (status only). Inspection never blocks the resume.
Promise.resolve(
agentGateway.inspectConversation?.(projectId, agentId, conversationId),
)
.then((details) => setResumeDetails(details ?? {}))
.catch(() => setResumeDetails({}));
return new Promise<TerminalHandle>((resolve, reject) => {
setPendingResume({ opts, onData, resolve, reject });
});
return doLaunch(opts, onData, conversationId ?? undefined);
}
: undefined;
/** "Reprendre" → launch resuming the existing conversation id. */
const onResume = () => {
const p = pendingResume;
if (!p) return;
setPendingResume(null);
setResumeDetails(null);
doLaunch(p.opts, p.onData, conversationId ?? undefined).then(p.resolve, p.reject);
};
/** "Nouvelle conversation" → clear the id first (Assign), then launch fresh. */
const onNewConversation = () => {
const p = pendingResume;
if (!p) return;
setPendingResume(null);
setResumeDetails(null);
// Clear the persisted conversation id BEFORE launching so the backend treats
// it as a fresh cell and assigns a new conversation.
void vm.setCellConversation(id, null);
doLaunch(p.opts, p.onData, undefined).then(p.resolve, p.reject);
};
// Agent cells re-attach through the agent gateway; plain cells fall back to
// the terminal gateway's reattach (handled by TerminalView's default).
const reattachOpener = agentGateway && agentId
@ -1639,14 +1564,6 @@ function LeafView({
</div>
</div>
)}
{pendingResume && (
<ResumeConversationPopup
agentWasRunning={agentWasRunning}
details={resumeDetails}
onResume={onResume}
onNewConversation={onNewConversation}
/>
)}
</div>
);
}