fix(workspace): stop the console remounting, and the idle polling re-renders

The Workspace page rendered `ConsoleArea` from two sites, picked by
`currentLane`. Every flip of that value unmounted the whole console
subtree and mounted it again at the other site: the terminal died, the
run history went with it, and `LaneConsolePane`'s lane-switch effect
wiped a half-typed prompt. It flipped on every page load (lanes arrive,
`refreshLanes` selects `lanes[0]`), on a lane being removed, and on any
refetch that re-picked the selection.

`lane-detail` is now always rendered and holds the only `ConsoleArea`;
the header and info block are the conditional parts. Same rule the
RunConsole already followed one level down — collapse with CSS, never by
unmounting — now enforced above it too.

Second half: every poller fed `setState` a brand-new object graph each
tick, re-rendering the lane strip, the card and the console area several
times a minute on a completely idle dashboard. `keepIfSame` keeps the
previous reference when the payload is value-identical, applied to the
5 s run/session poll, the lane list and counts, the `lane_update` WS
branch, and LaneCard's git/runtime/locks pollers.

Verified: npm run test:client (44 files / 454 tests), tsc --noEmit
clean, check-headers.sh clean. The Run screen snapshot was regenerated —
the diff is the added `<section>` wrapper and one indent level, no
content change.
This commit is contained in:
2026-09-04 10:48:22 +07:00
parent e109d2804c
commit 965c62c5cf
7 changed files with 547 additions and 462 deletions
@@ -0,0 +1,36 @@
/**
* @file keepIfSame.test.ts
* @description Covers the identity contract `keepIfSame` exists for: a polled
* payload that is value-identical must keep the PREVIOUS reference (so React
* bails out of the re-render), and any real change must yield the new one.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, expect } from "vitest";
import { keepIfSame } from "../keepIfSame";
describe("keepIfSame", () => {
it("keeps the previous reference when the fetched payload is value-identical", () => {
const prev = [{ id: 1, stage: "implement" }];
const fetched = [{ id: 1, stage: "implement" }];
expect(keepIfSame(fetched)(prev)).toBe(prev);
});
it("yields the new value when anything actually changed", () => {
const prev = [{ id: 1, stage: "implement" }];
const fetched = [{ id: 1, stage: "review" }];
expect(keepIfSame(fetched)(prev)).toBe(fetched);
});
it("yields the new value out of the null a poller starts from", () => {
const fetched = { available: false };
expect(keepIfSame(fetched)(null)).toBe(fetched);
});
it("keeps null when the payload is still null", () => {
expect(keepIfSame(null)(null)).toBeNull();
});
});