fix(frontend): refit différé des cellules terminal après mutation de layout (#61)
Le ResizeObserver de TerminalView ne déclenche pas toujours un événement utile quand une cellule voisine apparaît/disparaît (split/merge), forçant l'utilisateur à redimensionner la fenêtre pour rafraîchir le scaling xterm. useLayout expose un layoutVersion bumpé à chaque commit d'arbre (chargement initial inclus), relayé par LayoutGrid comme refitSignal à chaque TerminalView survivant pour déclencher fit.fit() sans rouvrir le PTY. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -917,6 +917,7 @@ function LeafView({
|
||||
onSessionId={(sid) => void vm.setSession(id, sid)}
|
||||
agentMode={agentId != null}
|
||||
portal={agentId != null ? portal : undefined}
|
||||
refitSignal={vm.layoutVersion}
|
||||
/>
|
||||
{/* Write-portal overlay (ARCHITECTURE §20.3 step b/e): while a delegation
|
||||
is being injected into the agent's PTY, a grey veil with a centred
|
||||
|
||||
@ -23,6 +23,15 @@ import { leaves, splitOp } from "./layout";
|
||||
export interface LayoutViewModel {
|
||||
/** The current layout tree, or `null` until loaded. */
|
||||
layout: LayoutTree | null;
|
||||
/**
|
||||
* Bumped every time a mutation (split/merge/resize/move/…) commits a new
|
||||
* tree, INCLUDING the initial load (ticket #61). `LayoutGrid` forwards it to
|
||||
* every mounted `TerminalView` as `refitSignal` so surviving terminals refit
|
||||
* after a structural change (split/merge) instead of waiting for a manual
|
||||
* window resize — their own `ResizeObserver` doesn't always fire a useful
|
||||
* event when a SIBLING cell appears or disappears.
|
||||
*/
|
||||
layoutVersion: number;
|
||||
/** Last error message, or `null`. */
|
||||
error: string | null;
|
||||
/** Whether a request is in flight. */
|
||||
@ -65,9 +74,18 @@ export function useLayout(
|
||||
): LayoutViewModel {
|
||||
const { layout: gateway, terminal } = useGateways();
|
||||
const [layout, setLayout] = useState<LayoutTree | null>(null);
|
||||
const [layoutVersion, setLayoutVersion] = useState(0);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
// Commits a freshly-loaded/mutated tree AND bumps the refit signal (ticket
|
||||
// #61) in one place, so every caller below (initial load, `mutate`,
|
||||
// `mutateChain`) stays a one-liner instead of repeating the pair.
|
||||
const applyTree = useCallback((tree: LayoutTree) => {
|
||||
setLayout(tree);
|
||||
setLayoutVersion((v) => v + 1);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// No project, or a DI subset without the layout gateway (e.g. a focused
|
||||
// test) → render nothing rather than crash the tab (mirrors TerminalView).
|
||||
@ -80,7 +98,7 @@ export function useLayout(
|
||||
gateway
|
||||
.loadLayout(projectId, layoutId)
|
||||
.then((tree) => {
|
||||
if (!cancelled) setLayout(tree);
|
||||
if (!cancelled) applyTree(tree);
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
if (!cancelled) setError(describe(e));
|
||||
@ -91,7 +109,7 @@ export function useLayout(
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [gateway, projectId, layoutId]);
|
||||
}, [gateway, projectId, layoutId, applyTree]);
|
||||
|
||||
const mutate = useCallback(
|
||||
async (operation: LayoutOperation) => {
|
||||
@ -99,14 +117,14 @@ export function useLayout(
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
setLayout(await gateway.mutateLayout(projectId, operation, layoutId));
|
||||
applyTree(await gateway.mutateLayout(projectId, operation, layoutId));
|
||||
} catch (e) {
|
||||
setError(describe(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[gateway, projectId, layoutId],
|
||||
[gateway, projectId, layoutId, applyTree],
|
||||
);
|
||||
|
||||
// Applies several operations in series, persisting each, but commits a single
|
||||
@ -124,14 +142,14 @@ export function useLayout(
|
||||
for (const op of operations) {
|
||||
tree = await gateway.mutateLayout(projectId, op, layoutId);
|
||||
}
|
||||
if (tree) setLayout(tree);
|
||||
if (tree) applyTree(tree);
|
||||
} catch (e) {
|
||||
setError(describe(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[gateway, projectId, layoutId],
|
||||
[gateway, projectId, layoutId, applyTree],
|
||||
);
|
||||
|
||||
const split = useCallback(
|
||||
@ -230,6 +248,7 @@ export function useLayout(
|
||||
|
||||
return {
|
||||
layout,
|
||||
layoutVersion,
|
||||
error,
|
||||
busy,
|
||||
split,
|
||||
|
||||
Reference in New Issue
Block a user