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:
@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { DestructiveLaneModal } from "./DestructiveLaneModal";
|
||||
import { api } from "../../lib/api";
|
||||
import { eventBus } from "../../lib/eventBus";
|
||||
import { keepIfSame } from "../../lib/keepIfSame";
|
||||
import type { Lane, LaneGitFacts, LaneRuntime, NamedLock } from "../../lib/types";
|
||||
|
||||
/** How often a mounted card re-reads its working-copy facts. Slow on purpose:
|
||||
@@ -49,12 +50,12 @@ function useLaneGitFacts(laneId: number): LaneGitFacts | null {
|
||||
api.lanes
|
||||
.git(laneId)
|
||||
.then((f) => {
|
||||
if (alive) setFacts(f);
|
||||
if (alive) setFacts(keepIfSame(f));
|
||||
})
|
||||
.catch(() => {
|
||||
// Silent: a card that cannot read git shows the rest of itself. An
|
||||
// error banner here would fire on every lane on every server blip.
|
||||
if (alive) setFacts({ available: false });
|
||||
if (alive) setFacts(keepIfSame<LaneGitFacts>({ available: false }));
|
||||
});
|
||||
};
|
||||
read();
|
||||
@@ -85,10 +86,10 @@ export function useLaneRuntime(laneId: number, bump: number): LaneRuntime | null
|
||||
api.lanes
|
||||
.runtime(laneId)
|
||||
.then((r) => {
|
||||
if (alive) setRuntime(r);
|
||||
if (alive) setRuntime(keepIfSame(r));
|
||||
})
|
||||
.catch(() => {
|
||||
if (alive) setRuntime({ available: false });
|
||||
if (alive) setRuntime(keepIfSame<LaneRuntime>({ available: false }));
|
||||
});
|
||||
};
|
||||
read();
|
||||
@@ -116,7 +117,7 @@ function useLaneLocks(laneSlot: number | null): NamedLock[] {
|
||||
api.locks
|
||||
.list()
|
||||
.then((data) => {
|
||||
if (alive) setLocks(data.locks.filter((l) => l.holder === holder));
|
||||
if (alive) setLocks(keepIfSame(data.locks.filter((l) => l.holder === holder)));
|
||||
})
|
||||
.catch(() => {
|
||||
/* fails silently, same contract as the git/runtime pollers */
|
||||
|
||||
Reference in New Issue
Block a user