fix(web): route la famille layout dans /api/invoke + fallback UX restauration — #212

Le web-server ne routait pas load_layout (UNKNOWN_COMMAND a l'ouverture
d'un projet web PC). Alignement de l'adapter web sur les contrats Tauri:
load_layout, mutate_layout, list_layouts, create_layout, rename_layout,
delete_layout, set_active_layout. useLayout ne propage plus l'erreur
brute: message utilisateur + fallback singleLeafTree.
This commit is contained in:
2026-08-15 11:40:00 +02:00
parent cf2f0dc917
commit 86f6c7ebf8
3 changed files with 332 additions and 34 deletions

View File

@ -35,6 +35,31 @@ function renderGrid(layout: MockLayoutGateway, projectId = "p1") {
}
describe("LayoutGrid (with MockLayoutGateway)", () => {
it("falls back to an empty workspace with a user-facing message when layout restore fails", async () => {
const layout = new MockLayoutGateway();
vi.spyOn(layout, "loadLayout").mockRejectedValueOnce(
new Error("unknown command: load_layout"),
);
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
renderGrid(layout);
const alert = await screen.findByRole("alert");
expect(alert.textContent).toContain(
"Impossible de restaurer l’espace de travail de ce projet.",
);
expect(alert.textContent).not.toContain("unknown command");
await waitFor(() => {
expect(screen.getAllByTestId("layout-leaf")).toHaveLength(1);
});
expect(warn).toHaveBeenCalledWith(
"Layout restore failed",
expect.objectContaining({ message: "unknown command: load_layout" }),
);
warn.mockRestore();
});
it("renders a single-leaf layout without throwing", async () => {
const layout = new MockLayoutGateway();
expect(() => renderGrid(layout)).not.toThrow();

View File

@ -20,9 +20,13 @@ import { useGateways } from "@/app/di";
import {
leaves,
replaceCustomPluginLayoutWithTerminal,
singleLeafTree,
splitOp,
} from "./layout";
const LAYOUT_RESTORE_ERROR =
"Impossible de restaurer l’espace de travail de ce projet. Un espace vide a été ouvert ; rechargez le projet pour réessayer.";
/** What the layout grid UI needs from this hook. */
export interface LayoutViewModel {
/** The current layout tree, or `null` until loaded. */
@ -109,13 +113,18 @@ export function useLayout(
}
let cancelled = false;
setBusy(true);
setError(null);
gateway
.loadLayout(projectId, layoutId)
.then((tree) => {
if (!cancelled) applyTree(tree);
})
.catch((e: unknown) => {
if (!cancelled) setError(describe(e));
if (!cancelled) {
console.warn("Layout restore failed", e);
applyTree(singleLeafTree());
setError(LAYOUT_RESTORE_ERROR);
}
})
.finally(() => {
if (!cancelled) setBusy(false);