refactor(client): rename currentProvide to currentProvideInfo

This commit is contained in:
imccyu
2026-07-28 11:09:55 +08:00
parent 71529aa7d2
commit b7f3cd3d78
9 changed files with 35 additions and 35 deletions

View File

@@ -157,7 +157,7 @@ export class SessionsService {
* host's `sessions.provide` feed), so a roster change under a stable * host's `sessions.provide` feed), so a roster change under a stable
* current id republishes the bundle instead of stranding mounted entries. * current id republishes the bundle instead of stranding mounted entries.
*/ */
readonly currentProvide: HostObservable<SessionMaybeProvideInfo> readonly currentProvideInfo: HostObservable<SessionMaybeProvideInfo>
/** /**
* Persisted selection cell (the durable half of `list.current`). Private on * Persisted selection cell (the durable half of `list.current`). Private on
@@ -174,10 +174,10 @@ export class SessionsService {
private readonly providers: SessionProvideDescriptor[] = [] private readonly providers: SessionProvideDescriptor[] = []
/** Static no-session projection, rebuilt only when the provider roster changes. */ /** Static no-session projection, rebuilt only when the provider roster changes. */
private maybeInfo: SessionMaybeProvideInfo private maybeInfo: SessionMaybeProvideInfo
/** Latest published {@link SessionsService.currentProvide} bundle (identity comparison dedupes republish). */ /** Latest published {@link SessionsService.currentProvideInfo} bundle (identity comparison dedupes republish). */
private currentProvideSnapshot: SessionMaybeProvideInfo private currentProvideInfoSnapshot: SessionMaybeProvideInfo
/** currentProvide subscribers (plain cell: bundles hold live Session sources, so no store freeze may touch them). */ /** currentProvideInfo subscribers (plain cell: bundles hold live Session sources, so no store freeze may touch them). */
private readonly currentProvideListeners = new Set<() => void>() private readonly currentProvideInfoListeners = new Set<() => void>()
/** /**
* The staged session id — follows `list.current` exactly, holding its last * The staged session id — follows `list.current` exactly, holding its last
* defined value across masked gaps (a transiently absent selection blanks * defined value across masked gaps (a transiently absent selection blanks
@@ -221,12 +221,12 @@ export class SessionsService {
resolve: binding => ({ hooks: { session: binding.session } }), resolve: binding => ({ hooks: { session: binding.session } }),
}) })
this.maybeInfo = this.materializeMaybeProvideInfo() this.maybeInfo = this.materializeMaybeProvideInfo()
this.currentProvideSnapshot = this.maybeInfo this.currentProvideInfoSnapshot = this.maybeInfo
this.currentProvide = { this.currentProvideInfo = {
getSnapshot: () => this.currentProvideSnapshot, getSnapshot: () => this.currentProvideInfoSnapshot,
subscribe: (fn) => { subscribe: (fn) => {
this.currentProvideListeners.add(fn) this.currentProvideInfoListeners.add(fn)
return () => { this.currentProvideListeners.delete(fn) } return () => { this.currentProvideInfoListeners.delete(fn) }
}, },
} }
rootCtx.reflect.provide('sessions', this, undefined) rootCtx.reflect.provide('sessions', this, undefined)
@@ -272,9 +272,9 @@ export class SessionsService {
*/ */
private projectCurrentProvide(): void { private projectCurrentProvide(): void {
const next = this.maybeProvideInfo(this.list.getSnapshot().current) const next = this.maybeProvideInfo(this.list.getSnapshot().current)
if (next === this.currentProvideSnapshot) return if (next === this.currentProvideInfoSnapshot) return
this.currentProvideSnapshot = next this.currentProvideInfoSnapshot = next
for (const fn of [...this.currentProvideListeners]) fn() for (const fn of [...this.currentProvideInfoListeners]) fn()
} }
/** Build the static no-session kit and reject duplicate declared names. */ /** 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 * Resolve one session's render-layer standard-props bundle (ctx never
* enters the render layer; the renderer subscribes to * 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 * no staging, no window side effects (StrictMode double-invokes and
* concurrent discarded passes must stay free). * concurrent discarded passes must stay free).
* @param id - session id. * @param id - session id.

View File

@@ -256,7 +256,7 @@ export class SlotsService extends Service {
entry.store === undefined ? undefined : this.resolveStore(entry.store as unknown as EngineStoreHandle, scopeKey), entry.store === undefined ? undefined : this.resolveStore(entry.store as unknown as EngineStoreHandle, scopeKey),
sessions: { sessions: {
list: sessions.list, list: sessions.list,
provideInfo: sessions.currentProvide, provideInfo: sessions.currentProvideInfo,
}, },
workspaces: { list: workspaces.list }, workspaces: { list: workspaces.list },
} }

View File

@@ -195,55 +195,55 @@ describe('cell (render-layer session kit)', () => {
expect(b.svc.provideInfo('ghost')).toBeUndefined() 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() const b = bench()
await feedList(b, [{ id: 's1' }, { id: 's2' }]) await feedList(b, [{ id: 's1' }, { id: 's2' }])
const absent = b.svc.currentProvide.getSnapshot() const absent = b.svc.currentProvideInfo.getSnapshot()
expect(absent.sessionId).toBeUndefined() expect(absent.sessionId).toBeUndefined()
expect(Object.hasOwn(absent.hooks, 'session')).toBe(true) expect(Object.hasOwn(absent.hooks, 'session')).toBe(true)
const notified = vi.fn() const notified = vi.fn()
b.svc.currentProvide.subscribe(notified) b.svc.currentProvideInfo.subscribe(notified)
b.svc.open(sid('s1')) 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) expect(notified).toHaveBeenCalledTimes(1)
b.svc.open(sid('s2')) 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) expect(notified).toHaveBeenCalledTimes(2)
b.svc.clear() b.svc.clear()
await Promise.resolve() // clearSelection projects through the manager notifier 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 () => { it('a provider roster change under a stable current id republishes the bundle', async () => {
const b = bench() const b = bench()
await feedList(b, [{ id: 's1' }]) await feedList(b, [{ id: 's1' }])
b.svc.open(sid('s1')) b.svc.open(sid('s1'))
const before = b.svc.currentProvide.getSnapshot() const before = b.svc.currentProvideInfo.getSnapshot()
const notified = vi.fn() const notified = vi.fn()
b.svc.currentProvide.subscribe(notified) b.svc.currentProvideInfo.subscribe(notified)
const source = { getSnapshot: () => 'live', subscribe: () => () => {} } const source = { getSnapshot: () => 'live', subscribe: () => () => {} }
const dispose = b.svc.provide({ const dispose = b.svc.provide({
hooks: ['extra'], hooks: ['extra'],
props: ['marker'], props: ['marker'],
resolve: () => ({ hooks: { extra: source }, props: { marker: 7 } }), 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).not.toBe(before)
expect(added).toMatchObject({ sessionId: 's1', props: { marker: 7 } }) expect(added).toMatchObject({ sessionId: 's1', props: { marker: 7 } })
expect(added.hooks['extra']).toBe(source) expect(added.hooks['extra']).toBe(source)
expect(notified).toHaveBeenCalledTimes(1) expect(notified).toHaveBeenCalledTimes(1)
dispose() dispose()
const removed = b.svc.currentProvide.getSnapshot() const removed = b.svc.currentProvideInfo.getSnapshot()
expect(removed).not.toBe(added) expect(removed).not.toBe(added)
expect(Object.hasOwn(removed.hooks, 'extra')).toBe(false) expect(Object.hasOwn(removed.hooks, 'extra')).toBe(false)
expect(notified).toHaveBeenCalledTimes(2) 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() const b = bench()
await feedList(b, [{ id: 's1' }]) await feedList(b, [{ id: 's1' }])
const notified = vi.fn() const notified = vi.fn()
const off = b.svc.currentProvide.subscribe(notified) const off = b.svc.currentProvideInfo.subscribe(notified)
off() off()
b.svc.open(sid('s1')) b.svc.open(sid('s1'))
expect(notified).not.toHaveBeenCalled() expect(notified).not.toHaveBeenCalled()

View File

@@ -103,7 +103,7 @@ function fakeSessions() {
const absentInfo = { sessionId: undefined, hooks: { session: undefined }, props: {} } const absentInfo = { sessionId: undefined, hooks: { session: undefined }, props: {} }
return { return {
list: { getSnapshot: () => state, subscribe: () => () => undefined }, list: { getSnapshot: () => state, subscribe: () => () => undefined },
currentProvide: { getSnapshot: () => absentInfo, subscribe: () => () => undefined }, currentProvideInfo: { getSnapshot: () => absentInfo, subscribe: () => () => undefined },
} }
} }

View File

@@ -93,7 +93,7 @@ async function bench() {
binding: (id: SessionId) => ({ sessionId: id, session: sessionFake, ctx: mint(id) }), binding: (id: SessionId) => ({ sessionId: id, session: sessionFake, ctx: mint(id) }),
scope: (id: SessionId) => mint(id), scope: (id: SessionId) => mint(id),
provideInfo: () => undefined, provideInfo: () => undefined,
currentProvide: { getSnapshot: () => absentInfo, subscribe: () => () => {} }, currentProvideInfo: { getSnapshot: () => absentInfo, subscribe: () => () => {} },
provide: (descriptor: TestProvider) => { providers.push(descriptor); return () => {} }, provide: (descriptor: TestProvider) => { providers.push(descriptor); return () => {} },
scopeOf, scopeOf,
sessionOf: (actx: Context) => (scopeOf(actx) === undefined ? undefined : sessionFake), sessionOf: (actx: Context) => (scopeOf(actx) === undefined ? undefined : sessionFake),

View File

@@ -38,7 +38,7 @@ async function bench() {
binding: vi.fn(), binding: vi.fn(),
scope: () => undefined, scope: () => undefined,
provideInfo: () => undefined, provideInfo: () => undefined,
currentProvide: { getSnapshot: () => absentInfo, subscribe: () => () => {} }, currentProvideInfo: { getSnapshot: () => absentInfo, subscribe: () => () => {} },
provide: vi.fn(() => () => {}), provide: vi.fn(() => () => {}),
create: vi.fn(), create: vi.fn(),
open: vi.fn(), open: vi.fn(),

View File

@@ -87,7 +87,7 @@ async function bench(snapshot: ConversationSnapshot) {
// Provide-channel contributions land in this bundle the way the runtime // Provide-channel contributions land in this bundle the way the runtime
// materializes them; the renderer host serves it through provideInfo. // materializes them; the renderer host serves it through provideInfo.
const provided: { hooks: Record<string, unknown>; props: Record<string, unknown> } = { hooks: {}, props: {} } const provided: { hooks: Record<string, unknown>; props: Record<string, unknown> } = { 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. // materialized on first render after the provide contributions landed.
let infoCell: { sessionId: SessionId; hooks: Record<string, unknown>; props: Record<string, unknown> } | undefined let infoCell: { sessionId: SessionId; hooks: Record<string, unknown>; props: Record<string, unknown> } | undefined
const sessionsFake = { const sessionsFake = {
@@ -106,7 +106,7 @@ async function bench(snapshot: ConversationSnapshot) {
provideInfo: (id: string) => (id === SID provideInfo: (id: string) => (id === SID
? { sessionId: SID, hooks: { session, ...provided.hooks }, props: provided.props } ? { sessionId: SID, hooks: { session, ...provided.hooks }, props: provided.props }
: undefined), : undefined),
currentProvide: { currentProvideInfo: {
getSnapshot: () => infoCell ??= { sessionId: SID, hooks: { session, ...provided.hooks }, props: provided.props }, getSnapshot: () => infoCell ??= { sessionId: SID, hooks: { session, ...provided.hooks }, props: provided.props },
subscribe: () => () => {}, subscribe: () => () => {},
}, },

View File

@@ -111,7 +111,7 @@ async function bench(nodes: ToolResultNode[]) {
binding: bindingOf, binding: bindingOf,
scope: () => actxFake, scope: () => actxFake,
provideInfo, provideInfo,
currentProvide: { currentProvideInfo: {
getSnapshot: () => provideInfo(SID), getSnapshot: () => provideInfo(SID),
subscribe: () => () => {}, subscribe: () => () => {},
}, },
@@ -255,7 +255,7 @@ describe('registrant load-order seam', () => {
binding: () => undefined, binding: () => undefined,
scope: () => undefined, scope: () => undefined,
provideInfo: () => undefined, provideInfo: () => undefined,
currentProvide: { currentProvideInfo: {
getSnapshot: () => ABSENT_INFO, getSnapshot: () => ABSENT_INFO,
subscribe: () => () => {}, subscribe: () => () => {},
}, },

View File

@@ -26,7 +26,7 @@ function bench(): Bench {
ids: [], byId: {}, current: undefined, phase: 'ready', ids: [], byId: {}, current: undefined, phase: 'ready',
}), }),
provideInfo: () => undefined, provideInfo: () => undefined,
currentProvide: { currentProvideInfo: {
getSnapshot: () => ABSENT_INFO, getSnapshot: () => ABSENT_INFO,
subscribe: () => () => {}, subscribe: () => () => {},
}, },