965c62c5cf
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.
35 lines
1.7 KiB
TypeScript
35 lines
1.7 KiB
TypeScript
/**
|
|
* @file keepIfSame.ts
|
|
* @description State-updater helper for polled data: keeps the previous value's
|
|
* identity when the freshly fetched payload is value-identical, so a background
|
|
* poll that found nothing new does not re-render the tree it feeds.
|
|
*
|
|
* Every poller in this app (`api.lanes.list`, `api.run.list`, `api.lanes.git`,
|
|
* …) returns a brand-new object graph on each tick. Passing that straight into
|
|
* `setState` re-renders every consumer several times a minute even when the
|
|
* dashboard is completely idle — visible as the whole page "refreshing".
|
|
*
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
/**
|
|
* Wraps a freshly fetched value as a functional state update that returns the
|
|
* previous state unchanged when the two are value-equal.
|
|
*
|
|
* Usage: `setLanes(keepIfSame(r.lanes))` instead of `setLanes(r.lanes)`.
|
|
*
|
|
* `prev` is typed wider than `T` so the same helper works for the `T | null`
|
|
* states the pollers start out in; the cast is sound because it only happens on
|
|
* the branch where `prev` serializes identically to `next`.
|
|
*
|
|
* @param next The value just fetched.
|
|
* @returns An updater for `setState`, yielding `prev` when nothing changed.
|
|
*/
|
|
export function keepIfSame<T>(next: T): (prev: T | null | undefined) => T {
|
|
// ponytail: JSON.stringify comparison — fine for the payloads this app polls
|
|
// (lane lists, run lists, a git-facts object: tens of small records, key order
|
|
// is stable because it comes from the same serializer every time). Swap for a
|
|
// structural deep-equal if a poller ever returns something big or key-unstable.
|
|
return (prev) => (JSON.stringify(prev) === JSON.stringify(next) ? (prev as T) : next);
|
|
}
|