Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

41 lines
1.7 KiB
TypeScript

import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
// Single React binding for workspace session engine subscriptions
// (docs/architecture/agent-gui-refactor-plan.md, sections 3.5 and 4.1).
//
// This is the only file in the package allowed to touch useSyncExternalStore
// (enforced by tools/scripts/check-agent-gui-degradation.mjs). Components
// subscribe through useEngineSelector so selector memoization and equality
// comparison are implemented exactly once, via React's official with-selector
// shim; hand-rolled subscriptions returning fresh references are the classic
// infinite re-render trap this file exists to contain.
/**
* Structural surface the binding needs from an engine instance. Matches
* AgentSessionEngine from @tutti-os/agent-activity-core, but stays structural
* so focused tests and future engine slices can bind without the full engine.
*
* Contract: `subscribe` and `getSnapshot` must be stable, this-free function
* references for the lifetime of the instance (the engine factory returns
* closures, satisfying this by construction). A fresh `subscribe` identity per
* render would force useSyncExternalStore to resubscribe on every render.
*/
export interface EngineStateStore<TState> {
getSnapshot(): TState;
subscribe(listener: () => void): () => void;
}
export function useEngineSelector<TState, TSelected>(
engine: EngineStateStore<TState>,
selector: (state: TState) => TSelected,
isEqual?: (a: TSelected, b: TSelected) => boolean
): TSelected {
return useSyncExternalStoreWithSelector(
engine.subscribe,
engine.getSnapshot,
engine.getSnapshot,
selector,
isEqual
);
}