The chat cut removed the only WebSocket and CI went before it, but comments still leaned on both: `?account=` was justified by the socket, job polling by an in-process WS registry, the e2e flows as "proxy-safe (none use the WebSocket)", and pyrightconfig named a CI container the engine contract test never checked. Each now says what holds today (the wallet gate, no push channel, the two contract test halves, the image build's `npm ci`, the gate).
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
import { useSyncExternalStore } from "react";
|
|
|
|
// A minimal observable value shared across React components AND non-React code (the api
|
|
// layer, the debug recorder, the error dialog, etc.). Replaces the hand-rolled
|
|
// `listeners`/`subscribe`/`emit` triad that several modules each re-implemented slightly differently.
|
|
export interface Store<T> {
|
|
get(): T;
|
|
set(next: T | ((prev: T) => T)): void;
|
|
subscribe(listener: () => void): () => void;
|
|
/** Subscribe from a component; re-renders on every change. Named `use…` on purpose: the React
|
|
* Compiler knows a hook only by that name, and a plain `use()` was compiled as an ordinary call —
|
|
* memoized, so it ran on the first render alone (usePlaylistActions then rendered fewer hooks
|
|
* and threw; Back to top never re-rendered). */
|
|
useValue(): T;
|
|
}
|
|
|
|
export function createStore<T>(initial: T): Store<T> {
|
|
let value = initial;
|
|
const listeners = new Set<() => void>();
|
|
const get = () => value;
|
|
const subscribe = (l: () => void) => {
|
|
listeners.add(l);
|
|
return () => {
|
|
listeners.delete(l);
|
|
};
|
|
};
|
|
return {
|
|
get,
|
|
set(next) {
|
|
value = typeof next === "function" ? (next as (p: T) => T)(value) : next;
|
|
listeners.forEach((l) => l());
|
|
},
|
|
subscribe,
|
|
useValue: () => useSyncExternalStore(subscribe, get, get),
|
|
};
|
|
}
|