@@ -646,6 +646,25 @@ const noMountedState: AutoMountedState = Object.freeze({
646646
647647type StateMount = ( renderer : Renderer , signal : AbortSignal ) => Promise < AutoMountedState > ;
648648
649+ /**
650+ * Marks the `state` handle a {@link mountTestState} mount hands out. A render
651+ * that receives one rebinds the shared owner to its own request signal — the
652+ * same `requestBindings({ signal })` a generated request scope performs — so
653+ * aborting that render stops its in-flight state operations without
654+ * disturbing the owner the other renders share.
655+ */
656+ const MOUNTED_TEST_STATE : unique symbol = Symbol . for ( 'agent-bundle/test-mounted-state' ) ;
657+
658+ type RebindMountedState = ( signal : AbortSignal ) => Promise < AutoMountedState > ;
659+
660+ type RebindableStateHandle <
661+ TState = unknown ,
662+ TEvents extends AgentState . AgentStateEventSchemas = AgentState . AgentStateEventSchemas ,
663+ > = AgentState . AgentStateHandle < TState , TEvents > & { readonly [ MOUNTED_TEST_STATE ] ?: RebindMountedState } ;
664+
665+ const mountedStateRebind = ( context : RenderRouteContext ) : RebindMountedState | undefined =>
666+ ( context . state as RebindableStateHandle | undefined ) ?. [ MOUNTED_TEST_STATE ] ;
667+
649668/**
650669 * Resolves how a manifest render mounts its state, without mounting it: the
651670 * loader lookup is harness wiring and fails here, while loading the state
@@ -657,6 +676,8 @@ const manifestStateMount = (
657676 provenance : RenderedRouteProvenance ,
658677 context : RenderRouteContext ,
659678) : StateMount => {
679+ const rebind = mountedStateRebind ( context ) ;
680+ if ( rebind !== undefined ) return ( _renderer , signal ) => rebind ( signal ) ;
660681 const descriptor = manifest ?. state ;
661682 if (
662683 manifest === undefined
@@ -854,9 +875,13 @@ const manifestStateDefinition = async (
854875 * removes; every other lifetime uses the memory driver. `renderRoute` and
855876 * `renderRouteEvents` honour a caller-supplied `state` and `noticeLedger`, so
856877 * spreading `context()` into each render's `context` is the whole wiring; a
857- * render that omits them still mounts its own isolated owner. The owner is
858- * held as one request's bindings, so a `request`-lifetime definition behaves
859- * as one long request. Always `close()` it (or use {@link withTestState}).
878+ * render that omits them still mounts its own isolated owner. Each render
879+ * that receives the handles binds the shared owner to its own request
880+ * signal, exactly as a generated request scope does, so a render's `signal`
881+ * still cancels its state operations; `read()`, `notices()`, and the handles
882+ * themselves are bound to `options.signal`. The owner is held as one request's
883+ * bindings, so a `request`-lifetime definition behaves as one long request.
884+ * Always `close()` it (or use {@link withTestState}).
860885 */
861886export const mountTestState = async <
862887 TState = unknown ,
@@ -877,11 +902,30 @@ export const mountTestState = async <
877902 await opened . dispose ( ) ;
878903 throw error ;
879904 }
905+ let closed = false ;
906+ // A render that receives these handles rebinds the shared owner to its own
907+ // request signal (see MOUNTED_TEST_STATE). A `request`-lifetime owner opens
908+ // fresh stores per binding, so it is not rebound: the mount's one binding
909+ // is the request every render shares.
910+ const rebind : RebindMountedState = async ( signal ) => {
911+ if ( closed || definition . lifetime === 'request' ) return noMountedState ;
912+ const request = await opened . owner . requestBindings ( { signal } ) ;
913+ return Object . freeze ( {
914+ context : { noticeLedger : request . noticeLedger , state : request . state } ,
915+ close : request . close ,
916+ } ) ;
917+ } ;
918+ const state : RebindableStateHandle < TState , TEvents > = Object . freeze ( {
919+ lifetime : bindings . state . lifetime ,
920+ changes : bindings . state . changes ,
921+ dispatch : bindings . state . dispatch ,
922+ read : bindings . state . read ,
923+ [ MOUNTED_TEST_STATE ] : rebind ,
924+ } ) ;
880925 const context : MountedTestStateContext < TState , TEvents > = Object . freeze ( {
881926 noticeLedger : bindings . noticeLedger ,
882- state : bindings . state ,
927+ state,
883928 } ) ;
884- let closed = false ;
885929 return Object . freeze ( {
886930 ...context ,
887931 async close ( ) {
0 commit comments