generated from coulomb/repo-seed
Capture mode state lived only in React memory and was lost when reopening a session or remounting EngineProvider. - Add per-session localStorage capture snapshot (schema, values, links) - Restore on session mount; persist on field/schema/link changes - Seed binder links from storage without spurious bus events - Clean up capture key when session is deleted - Integration test for reload persistence
120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
/**
|
|
* BinderProvider — composition root for the binder subsystem.
|
|
*
|
|
* Wires the four binder concerns (rect registry, binding service, link
|
|
* repo, active state machine) into one provider so a single mount inside
|
|
* the EngineProvider gives every binder consumer (FormRenderer, evidence
|
|
* picker, SVG overlay) what it needs.
|
|
*
|
|
* The provider is split out from the engine because in a future
|
|
* subsystem-extraction these will live in separate packages — the engine
|
|
* will publish only the event bus and the engine services, while
|
|
* `evidence-binder` will export this provider.
|
|
*/
|
|
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
import type { EvidenceLink } from "@shared/evidence-link";
|
|
|
|
import type { EventBus } from "@engine/events";
|
|
|
|
import {
|
|
ActiveStateProvider,
|
|
useActiveState,
|
|
} from "./state/active";
|
|
import {
|
|
createInMemoryLinkRepo,
|
|
type EvidenceLinkRepository,
|
|
} from "./repos/in-memory-links";
|
|
import {
|
|
createBindingService,
|
|
type BindingService,
|
|
} from "./services/bindings";
|
|
import {
|
|
RectRegistryProvider,
|
|
createRectRegistryContextValue,
|
|
type RectRegistryContextValue,
|
|
} from "./visual-guide/react-hooks";
|
|
|
|
export interface BinderServices {
|
|
readonly links: EvidenceLinkRepository;
|
|
readonly bindings: BindingService;
|
|
readonly rect: RectRegistryContextValue;
|
|
}
|
|
|
|
const BinderServicesContext = createContext<BinderServices | null>(null);
|
|
|
|
export function useBinder(): BinderServices {
|
|
const ctx = useContext(BinderServicesContext);
|
|
if (!ctx) throw new Error("useBinder: missing <BinderProvider />");
|
|
return ctx;
|
|
}
|
|
|
|
export interface BinderProviderProps {
|
|
readonly children: ReactNode;
|
|
/**
|
|
* The engine's event bus, threaded in by the composition root so the
|
|
* binder can emit §4 events without importing work/EngineContext
|
|
* (work cannot be a dependency of binder — see DependencyMap §2).
|
|
*/
|
|
readonly bus: EventBus;
|
|
/**
|
|
* Tests can inject a pre-built service set; production constructs a
|
|
* fresh one. The rect registry is *always* fresh per provider mount
|
|
* because its observers attach to the current `window`.
|
|
*/
|
|
readonly services?: Omit<BinderServices, "rect">;
|
|
/**
|
|
* Restored evidence links for this session. Seeded directly into the
|
|
* repo (no bus events) so reload does not spuriously re-emit
|
|
* `EvidenceLinkCreated`.
|
|
*/
|
|
readonly initialLinks?: readonly EvidenceLink[];
|
|
}
|
|
|
|
export function BinderProvider({
|
|
children,
|
|
bus,
|
|
services,
|
|
initialLinks,
|
|
}: BinderProviderProps) {
|
|
const built = useMemo<BinderServices>(() => {
|
|
const links = services?.links ?? createInMemoryLinkRepo();
|
|
const bindings = services?.bindings ?? createBindingService(links, bus);
|
|
const rect = createRectRegistryContextValue();
|
|
return { links, bindings, rect };
|
|
}, [bus, services]);
|
|
|
|
useEffect(() => {
|
|
if (!initialLinks?.length || services?.links) return;
|
|
for (const link of initialLinks) {
|
|
if (!built.links.get(link.id)) {
|
|
built.links.create(link);
|
|
}
|
|
}
|
|
}, [built.links, initialLinks, services?.links]);
|
|
|
|
// Disconnect rect observers + listeners on unmount.
|
|
useEffect(() => {
|
|
return () => {
|
|
built.rect.observer.disconnect();
|
|
};
|
|
}, [built.rect]);
|
|
|
|
return (
|
|
<BinderServicesContext.Provider value={built}>
|
|
<RectRegistryProvider value={built.rect}>
|
|
<ActiveStateProvider bus={bus}>{children}</ActiveStateProvider>
|
|
</RectRegistryProvider>
|
|
</BinderServicesContext.Provider>
|
|
);
|
|
}
|
|
|
|
export { useActiveState };
|