302ee80b6f
Priority between the overlapping signals — a session can be working and unread and running a background job at once — was resolved at the call site from five separate membership lookups, which is how surfaces drift apart. `$sessionDotStateById` does it once and hands each surface a single answer. The dot's visual language collapses to three colors on one fill/hollow axis with nothing moving. Motion on a six-pixel circle can only say "something is happening", which the row's arc already says better, and it cost a repaint per frame on every row at once; filled now means producing and hollow means open but quiet. Working and stalled had differed by 30% opacity and were in practice the same dot. A settled session paints its project color or nothing, rather than a grey mark of the same weight as a real status next to every resting row. The switcher had grown its own dot with its own three states, so it disagreed with the sidebar on the same session. It renders the shared one now.
18 lines
886 B
TypeScript
18 lines
886 B
TypeScript
/** Keep `prev`'s reference when it's element-equal to `next`, so a nanostores
|
|
* `computed` (notifies on `!==`) skips the emit when its projected list didn't
|
|
* actually change — e.g. status-id sets recomputed on every stream delta.
|
|
* `next` is frozen: the ref is shared across ticks, so an in-place mutation
|
|
* would corrupt the cache — fail loud instead. */
|
|
export const stableArray = <T>(prev: readonly T[], next: T[]): readonly T[] =>
|
|
prev.length === next.length && prev.every((v, i) => v === next[i]) ? prev : Object.freeze(next)
|
|
|
|
/** {@link stableArray} for a keyed projection. */
|
|
export const stableRecord = <T>(
|
|
prev: Readonly<Record<string, T>>,
|
|
next: Record<string, T>
|
|
): Readonly<Record<string, T>> => {
|
|
const keys = Object.keys(next)
|
|
|
|
return keys.length === Object.keys(prev).length && keys.every(k => prev[k] === next[k]) ? prev : Object.freeze(next)
|
|
}
|