import { useEffect, type ReactNode } from 'react'; import { setJourneyTags, type JourneyDef } from '../../core'; import { moduleRuntime } from '../../core/runtime'; import type { TagValue } from '../../core/types'; import { JourneyNameContext } from './context'; import { stableEndpointKey, stableExpectedKey, stableTagsKey } from './stable-config-keys'; type JourneyScopeDef = JourneyDef | { config: JourneyDef }; /** * Opens a journey while the subtree is mounted; Excludes on unmount. * `name` is the lifecycle key — changing it excludes the previous instance * and opens a new one. Other config fields and `tags` update the open instance * in place (timeouts re-arm; they do not restart). * * Pass a {@link JourneyDef} (``) or spread a handle * (`` reads `handle.config`). * * React 18 Strict Mode (development) remounts once: expect an `excluded` emission * then a fresh open. Production single-mount does not. */ export function JourneyScope({ tags, children, ...rest }: JourneyScopeDef & { tags?: Record; children: ReactNode; }) { const def: JourneyDef = 'config' in rest ? rest.config : rest; const { name, timeoutMs, stepTimeoutMs, slowRequestMs, endpoints, expected, service, team, group, maxSteps, } = def; const endpointKey = stableEndpointKey(endpoints); const expectedKey = stableExpectedKey(expected); const configTagsKey = stableTagsKey(def.tags); useEffect(() => { moduleRuntime.startJourney(def); const opened = moduleRuntime.getOpenJourney(name); return () => { if (opened) { moduleRuntime.excludeJourney(opened); } }; // eslint-disable-next-line react-hooks/exhaustive-deps -- name is the lifecycle key }, [name]); useEffect(() => { if (tags) { setJourneyTags(name, tags); } }, [name, tags]); useEffect(() => { moduleRuntime.updateOpenJourney(def); // eslint-disable-next-line react-hooks/exhaustive-deps -- field-level live updates; name-change is the other effect }, [ name, timeoutMs, stepTimeoutMs, slowRequestMs, endpointKey, expectedKey, service, team, group, maxSteps, configTagsKey, ]); return {children}; }