fix(typert): preserve remote lookup semantics

This commit is contained in:
imccyu
2026-08-07 14:51:49 +08:00
parent 2fe4a53557
commit d941350227
35 changed files with 425 additions and 65 deletions

View File

@@ -70,8 +70,6 @@ function isRemoteError(value: unknown): value is { readonly code: string; readon
export function apply(ctx: ClientContext): void {
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-goal: dictionaries')
const { goals } = ctx.api
const sessions = ctx.sessions
/** The session's current projected CAS ref, read at verb call time (no staleness fence: the RPC's CAS is the guard). */
@@ -96,22 +94,22 @@ export function apply(ctx: ClientContext): void {
onEdit: async (objective) => {
const ref = refOf(sessionId)
if (ref === undefined) return noCurrentGoal
return settle(goals.edit(sessionId, ref, { objective }))
return settle(ctx.api.goals.edit(sessionId, ref, { objective }))
},
onPause: async () => {
const ref = refOf(sessionId)
if (ref === undefined) return noCurrentGoal
return settle(goals.pause(sessionId, ref))
return settle(ctx.api.goals.pause(sessionId, ref))
},
onResume: async () => {
const ref = refOf(sessionId)
if (ref === undefined) return noCurrentGoal
return settle(goals.resume(sessionId, ref))
return settle(ctx.api.goals.resume(sessionId, ref))
},
onClear: async () => {
const ref = refOf(sessionId)
if (ref === undefined) return noCurrentGoal
return settle(goals.clear(sessionId, ref))
return settle(ctx.api.goals.clear(sessionId, ref))
},
}),
}, GoalDock))

View File

@@ -64,12 +64,16 @@ async function bench(options: {
}
}
const ref = { id: 'g-1', revision: 3 }
ctx.provide('api', { goals: {
edit: answer('goals/edit', { ref }),
pause: answer('goals/pause', { ref }),
resume: answer('goals/resume', { ref }),
clear: answer('goals/clear', ref),
} })
const goals = (prefix: string) => ({
edit: answer(`${prefix}/edit`, { ref }),
pause: answer(`${prefix}/pause`, { ref }),
resume: answer(`${prefix}/resume`, { ref }),
clear: answer(`${prefix}/clear`, ref),
})
let activeGoals = goals('goals')
ctx.provide('api', {
get goals() { return activeGoals },
})
await ctx.plugin(SlotsService).await()
ctx.slots.register({
name: 'root', children: { 'conversation.input.dock': { kind: 'list', scope: 'session' } },
@@ -90,6 +94,7 @@ async function bench(options: {
ctx,
fiber,
calls,
remountGoals: () => { activeGoals = goals('remounted-goals') },
entry: () => {
const entry = ctx.slots.entries('conversation.input.dock')[0]
if (entry === undefined) return undefined
@@ -126,6 +131,16 @@ describe('ui-goal browser plugin', () => {
expect(b.calls[3]?.args).toEqual(['s1', ref])
})
it('verbs read a remounted Remote namespace at action time', async () => {
const b = await bench({ projection: makeProjection() })
await b.fiber.await()
const verbs = b.entry()!.inject!(sid('s1'))
b.remountGoals()
expect(await verbs.onPause()).toEqual({ ok: true })
expect(b.calls).toMatchObject([{ method: 'remounted-goals/pause' }])
})
it('a null or absent projection short-circuits every verb without touching the wire', async () => {
for (const projection of [null, undefined]) {
const b = await bench({ projection })