diff --git a/packages/client/runtime/src/client/sessions/service.ts b/packages/client/runtime/src/client/sessions/service.ts index 03eca7724f..b5ce1905eb 100644 --- a/packages/client/runtime/src/client/sessions/service.ts +++ b/packages/client/runtime/src/client/sessions/service.ts @@ -157,7 +157,7 @@ export class SessionsService { * host's `sessions.provide` feed), so a roster change under a stable * current id republishes the bundle instead of stranding mounted entries. */ - readonly currentProvide: HostObservable + readonly currentProvideInfo: HostObservable /** * Persisted selection cell (the durable half of `list.current`). Private on @@ -174,10 +174,10 @@ export class SessionsService { private readonly providers: SessionProvideDescriptor[] = [] /** Static no-session projection, rebuilt only when the provider roster changes. */ private maybeInfo: SessionMaybeProvideInfo - /** Latest published {@link SessionsService.currentProvide} bundle (identity comparison dedupes republish). */ - private currentProvideSnapshot: SessionMaybeProvideInfo - /** currentProvide subscribers (plain cell: bundles hold live Session sources, so no store freeze may touch them). */ - private readonly currentProvideListeners = new Set<() => void>() + /** Latest published {@link SessionsService.currentProvideInfo} bundle (identity comparison dedupes republish). */ + private currentProvideInfoSnapshot: SessionMaybeProvideInfo + /** currentProvideInfo subscribers (plain cell: bundles hold live Session sources, so no store freeze may touch them). */ + private readonly currentProvideInfoListeners = new Set<() => void>() /** * The staged session id — follows `list.current` exactly, holding its last * defined value across masked gaps (a transiently absent selection blanks @@ -221,12 +221,12 @@ export class SessionsService { resolve: binding => ({ hooks: { session: binding.session } }), }) this.maybeInfo = this.materializeMaybeProvideInfo() - this.currentProvideSnapshot = this.maybeInfo - this.currentProvide = { - getSnapshot: () => this.currentProvideSnapshot, + this.currentProvideInfoSnapshot = this.maybeInfo + this.currentProvideInfo = { + getSnapshot: () => this.currentProvideInfoSnapshot, subscribe: (fn) => { - this.currentProvideListeners.add(fn) - return () => { this.currentProvideListeners.delete(fn) } + this.currentProvideInfoListeners.add(fn) + return () => { this.currentProvideInfoListeners.delete(fn) } }, } rootCtx.reflect.provide('sessions', this, undefined) @@ -272,9 +272,9 @@ export class SessionsService { */ private projectCurrentProvide(): void { const next = this.maybeProvideInfo(this.list.getSnapshot().current) - if (next === this.currentProvideSnapshot) return - this.currentProvideSnapshot = next - for (const fn of [...this.currentProvideListeners]) fn() + if (next === this.currentProvideInfoSnapshot) return + this.currentProvideInfoSnapshot = next + for (const fn of [...this.currentProvideInfoListeners]) fn() } /** Build the static no-session kit and reject duplicate declared names. */ @@ -443,7 +443,7 @@ export class SessionsService { /** * Resolve one session's render-layer standard-props bundle (ctx never * enters the render layer; the renderer subscribes to - * {@link SessionsService.currentProvide}). Pure resolution — render-safe: + * {@link SessionsService.currentProvideInfo}). Pure resolution — render-safe: * no staging, no window side effects (StrictMode double-invokes and * concurrent discarded passes must stay free). * @param id - session id. diff --git a/packages/client/runtime/src/client/slots.ts b/packages/client/runtime/src/client/slots.ts index 413668b2f8..d18377e052 100644 --- a/packages/client/runtime/src/client/slots.ts +++ b/packages/client/runtime/src/client/slots.ts @@ -256,7 +256,7 @@ export class SlotsService extends Service { entry.store === undefined ? undefined : this.resolveStore(entry.store as unknown as EngineStoreHandle, scopeKey), sessions: { list: sessions.list, - provideInfo: sessions.currentProvide, + provideInfo: sessions.currentProvideInfo, }, workspaces: { list: workspaces.list }, } diff --git a/packages/client/runtime/tests/sessions-service.spec.ts b/packages/client/runtime/tests/sessions-service.spec.ts index 2f90c1c301..b97dac3d78 100644 --- a/packages/client/runtime/tests/sessions-service.spec.ts +++ b/packages/client/runtime/tests/sessions-service.spec.ts @@ -195,55 +195,55 @@ describe('cell (render-layer session kit)', () => { expect(b.svc.provideInfo('ghost')).toBeUndefined() }) - it('currentProvide follows selection: absent projection ↔ definite bundle, notified on each move', async () => { + it('currentProvideInfo follows selection: absent projection ↔ definite bundle, notified on each move', async () => { const b = bench() await feedList(b, [{ id: 's1' }, { id: 's2' }]) - const absent = b.svc.currentProvide.getSnapshot() + const absent = b.svc.currentProvideInfo.getSnapshot() expect(absent.sessionId).toBeUndefined() expect(Object.hasOwn(absent.hooks, 'session')).toBe(true) const notified = vi.fn() - b.svc.currentProvide.subscribe(notified) + b.svc.currentProvideInfo.subscribe(notified) b.svc.open(sid('s1')) - expect(b.svc.currentProvide.getSnapshot()).toBe(b.svc.provideInfo('s1')) + expect(b.svc.currentProvideInfo.getSnapshot()).toBe(b.svc.provideInfo('s1')) expect(notified).toHaveBeenCalledTimes(1) b.svc.open(sid('s2')) - expect(b.svc.currentProvide.getSnapshot()).toBe(b.svc.provideInfo('s2')) + expect(b.svc.currentProvideInfo.getSnapshot()).toBe(b.svc.provideInfo('s2')) expect(notified).toHaveBeenCalledTimes(2) b.svc.clear() await Promise.resolve() // clearSelection projects through the manager notifier - expect(b.svc.currentProvide.getSnapshot().sessionId).toBeUndefined() + expect(b.svc.currentProvideInfo.getSnapshot().sessionId).toBeUndefined() }) it('a provider roster change under a stable current id republishes the bundle', async () => { const b = bench() await feedList(b, [{ id: 's1' }]) b.svc.open(sid('s1')) - const before = b.svc.currentProvide.getSnapshot() + const before = b.svc.currentProvideInfo.getSnapshot() const notified = vi.fn() - b.svc.currentProvide.subscribe(notified) + b.svc.currentProvideInfo.subscribe(notified) const source = { getSnapshot: () => 'live', subscribe: () => () => {} } const dispose = b.svc.provide({ hooks: ['extra'], props: ['marker'], resolve: () => ({ hooks: { extra: source }, props: { marker: 7 } }), }) - const added = b.svc.currentProvide.getSnapshot() + const added = b.svc.currentProvideInfo.getSnapshot() expect(added).not.toBe(before) expect(added).toMatchObject({ sessionId: 's1', props: { marker: 7 } }) expect(added.hooks['extra']).toBe(source) expect(notified).toHaveBeenCalledTimes(1) dispose() - const removed = b.svc.currentProvide.getSnapshot() + const removed = b.svc.currentProvideInfo.getSnapshot() expect(removed).not.toBe(added) expect(Object.hasOwn(removed.hooks, 'extra')).toBe(false) expect(notified).toHaveBeenCalledTimes(2) }) - it('an unsubscribed currentProvide listener stops receiving notifications', async () => { + it('an unsubscribed currentProvideInfo listener stops receiving notifications', async () => { const b = bench() await feedList(b, [{ id: 's1' }]) const notified = vi.fn() - const off = b.svc.currentProvide.subscribe(notified) + const off = b.svc.currentProvideInfo.subscribe(notified) off() b.svc.open(sid('s1')) expect(notified).not.toHaveBeenCalled() diff --git a/packages/client/runtime/tests/slots-service.spec.ts b/packages/client/runtime/tests/slots-service.spec.ts index e9d6d3e12b..07e03b6e9d 100644 --- a/packages/client/runtime/tests/slots-service.spec.ts +++ b/packages/client/runtime/tests/slots-service.spec.ts @@ -103,7 +103,7 @@ function fakeSessions() { const absentInfo = { sessionId: undefined, hooks: { session: undefined }, props: {} } return { list: { getSnapshot: () => state, subscribe: () => () => undefined }, - currentProvide: { getSnapshot: () => absentInfo, subscribe: () => () => undefined }, + currentProvideInfo: { getSnapshot: () => absentInfo, subscribe: () => () => undefined }, } } diff --git a/packages/client/ui-conversation/tests/apply-inject.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.spec.tsx index 37b824c906..885cdd2523 100644 --- a/packages/client/ui-conversation/tests/apply-inject.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.spec.tsx @@ -93,7 +93,7 @@ async function bench() { binding: (id: SessionId) => ({ sessionId: id, session: sessionFake, ctx: mint(id) }), scope: (id: SessionId) => mint(id), provideInfo: () => undefined, - currentProvide: { getSnapshot: () => absentInfo, subscribe: () => () => {} }, + currentProvideInfo: { getSnapshot: () => absentInfo, subscribe: () => () => {} }, provide: (descriptor: TestProvider) => { providers.push(descriptor); return () => {} }, scopeOf, sessionOf: (actx: Context) => (scopeOf(actx) === undefined ? undefined : sessionFake), diff --git a/packages/client/ui-conversation/tests/chat-apply.spec.tsx b/packages/client/ui-conversation/tests/chat-apply.spec.tsx index 818a6bf620..dab04e94c7 100644 --- a/packages/client/ui-conversation/tests/chat-apply.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-apply.spec.tsx @@ -38,7 +38,7 @@ async function bench() { binding: vi.fn(), scope: () => undefined, provideInfo: () => undefined, - currentProvide: { getSnapshot: () => absentInfo, subscribe: () => () => {} }, + currentProvideInfo: { getSnapshot: () => absentInfo, subscribe: () => () => {} }, provide: vi.fn(() => () => {}), create: vi.fn(), open: vi.fn(), diff --git a/packages/client/ui-conversation/tests/chat-code-subcalls.spec.tsx b/packages/client/ui-conversation/tests/chat-code-subcalls.spec.tsx index d7f523b028..5c40f282db 100644 --- a/packages/client/ui-conversation/tests/chat-code-subcalls.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-code-subcalls.spec.tsx @@ -87,7 +87,7 @@ async function bench(snapshot: ConversationSnapshot) { // Provide-channel contributions land in this bundle the way the runtime // materializes them; the renderer host serves it through provideInfo. const provided: { hooks: Record; props: Record } = { hooks: {}, props: {} } - // Identity-stable currentProvide snapshot (uSES getSnapshot contract), + // Identity-stable currentProvideInfo snapshot (uSES getSnapshot contract), // materialized on first render after the provide contributions landed. let infoCell: { sessionId: SessionId; hooks: Record; props: Record } | undefined const sessionsFake = { @@ -106,7 +106,7 @@ async function bench(snapshot: ConversationSnapshot) { provideInfo: (id: string) => (id === SID ? { sessionId: SID, hooks: { session, ...provided.hooks }, props: provided.props } : undefined), - currentProvide: { + currentProvideInfo: { getSnapshot: () => infoCell ??= { sessionId: SID, hooks: { session, ...provided.hooks }, props: provided.props }, subscribe: () => () => {}, }, diff --git a/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx b/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx index 117a7108c9..ec713d4bb1 100644 --- a/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx @@ -111,7 +111,7 @@ async function bench(nodes: ToolResultNode[]) { binding: bindingOf, scope: () => actxFake, provideInfo, - currentProvide: { + currentProvideInfo: { getSnapshot: () => provideInfo(SID), subscribe: () => () => {}, }, @@ -255,7 +255,7 @@ describe('registrant load-order seam', () => { binding: () => undefined, scope: () => undefined, provideInfo: () => undefined, - currentProvide: { + currentProvideInfo: { getSnapshot: () => ABSENT_INFO, subscribe: () => () => {}, }, diff --git a/packages/client/ui-conversation/tests/selection-survival.spec.ts b/packages/client/ui-conversation/tests/selection-survival.spec.ts index 19988eeab9..533d5ce730 100644 --- a/packages/client/ui-conversation/tests/selection-survival.spec.ts +++ b/packages/client/ui-conversation/tests/selection-survival.spec.ts @@ -26,7 +26,7 @@ function bench(): Bench { ids: [], byId: {}, current: undefined, phase: 'ready', }), provideInfo: () => undefined, - currentProvide: { + currentProvideInfo: { getSnapshot: () => ABSENT_INFO, subscribe: () => () => {}, },