refactor(api): expose traced remote namespaces
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-goal/README.md
|
||||
README.md: b99aaf624a7d669879ba668938ee455e3cdc68ad
|
||||
README.zh.md: 3d823d013066bc912398f61c85553887e05ca3b4
|
||||
README.md: a53fb3a89eaee364cb025ca728ca42ce934887b0
|
||||
README.zh.md: 1ad9f50aee5b103f6455e4d4b7d29fa9eb29a108
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Goal surface plugin, browser half: the `GoalBar` strip is the second standalone card in the `conversation.input.dock` composer-context stack (order 10, after Todo and before Queue). The live goal arrives through `useProjection('goal')` — the host-computed whole value seeded by the history tail page and updated by `session/projection` frames — so the plugin owns no domain store, refresh chain, or event listener. The slot inject face carries only the four mutation verbs (edit / pause / resume / clear through `ctx.api.goals` — an active goal offers the pause action, a paused one resume); each reads the CAS ref from the session's current projected value at call time and surfaces the rejected Remote error inline. The strip single-flights mutations synchronously because React's pending render cannot fence same-frame clicks; after a successful clear it immediately suppresses that exact goal id while the authoritative null projection catches up. Goal creation stays on the `/goal` host command; loading, absent, completed, and successfully cleared goals render nothing.
|
||||
Goal surface plugin, browser half: the `GoalBar` strip is the second standalone card in the `conversation.input.dock` composer-context stack (order 10, after Todo and before Queue). The live goal arrives through `useProjection('goal')` — the host-computed whole value seeded by the history tail page and updated by `session/projection` frames — so the plugin owns no domain store, refresh chain, or event listener. The slot inject face carries only the four mutation verbs (edit / pause / resume / clear through `ctx.remote.goals` — an active goal offers the pause action, a paused one resume); each reads the CAS ref from the session's current projected value at call time and surfaces the rejected Remote error inline. The strip single-flights mutations synchronously because React's pending render cannot fence same-frame clicks; after a successful clear it immediately suppresses that exact goal id while the authoritative null projection catches up. Goal creation stays on the `/goal` host command; loading, absent, completed, and successfully cleared goals render nothing.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`), the `GoalBar`/`GoalDock` components, and the injected verb face types.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
Goal 界面插件(浏览器端部分):`GoalBar` 条带是 `conversation.input.dock` composer 上下文堆栈中的第二张独立卡片(order 10,位于 Todo 之后、Queue 之前)。活值经 `useProjection('goal')` 到达——host 计算的全量值由历史尾页播种、由 `session/projection` 帧更新——因此本插件不持有领域 store、不设刷新链、不挂事件监听。slot 注入面只携带四个变更动词(edit / pause / resume / clear,经 `ctx.api.goals` 调用——active 的 goal 提供暂停动作,paused 的提供恢复);每个动词在调用时从会话当前投影值读取 CAS ref,并将 Remote 调用的拒绝错误内联呈现。由于 React 的 pending 渲染无法拦住同一帧内的点击,横条会同步为变更建立 single-flight 防护;清除成功后,会立即抑制该 goal id 对应的目标显示,直到权威的 null 投影追上。goal 的创建仍归 `/goal` host 命令;加载中、无 goal、已完成和已成功清除的 goal 一律不渲染。
|
||||
Goal 界面插件(浏览器端部分):`GoalBar` 条带是 `conversation.input.dock` composer 上下文堆栈中的第二张独立卡片(order 10,位于 Todo 之后、Queue 之前)。活值经 `useProjection('goal')` 到达——host 计算的全量值由历史尾页播种、由 `session/projection` 帧更新——因此本插件不持有领域 store、不设刷新链、不挂事件监听。slot 注入面只携带四个变更动词(edit / pause / resume / clear,经 `ctx.remote.goals` 调用——active 的 goal 提供暂停动作,paused 的提供恢复);每个动词在调用时从会话当前投影值读取 CAS ref,并将 Remote 调用的拒绝错误内联呈现。由于 React 的 pending 渲染无法拦住同一帧内的点击,横条会同步为变更建立 single-flight 防护;清除成功后,会立即抑制该 goal id 对应的目标显示,直到权威的 null 投影追上。goal 的创建仍归 `/goal` host 命令;加载中、无 goal、已完成和已成功清除的 goal 一律不渲染。
|
||||
|
||||
`/client` 的导出接口包括插件本体(`apply`/`inject`)、`GoalBar`/`GoalDock` 组件与注入动词面类型。
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Goal creation stays on the /goal host command.
|
||||
*/
|
||||
import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
// Type-only: pulls the generated Remote API and ctx.api merge through the Client assembly boundary.
|
||||
// Type-only: pulls the generated Remote API and ctx.remote merge through the Client assembly boundary.
|
||||
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
||||
// Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry).
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
@@ -36,7 +36,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
const NS = 'goal'
|
||||
|
||||
/** Required services: slots for the dock entry, sessions for the projected ref, API for Remote mutations, locale for the copy. */
|
||||
export const inject = ['slots', 'sessions', 'api', 'locale']
|
||||
export const inject = ['slots', 'sessions', 'remote', 'remote.goals', 'locale']
|
||||
|
||||
/** Map one generated Remote call, including synchronous namespace lookup failures, onto the strip's inline-render shape. */
|
||||
async function settle(invoke: () => Promise<unknown>): Promise<GoalActionResult> {
|
||||
@@ -94,22 +94,22 @@ export function apply(ctx: ClientContext): void {
|
||||
onEdit: async (objective) => {
|
||||
const ref = refOf(sessionId)
|
||||
if (ref === undefined) return noCurrentGoal
|
||||
return settle(() => ctx.api.goals.edit(sessionId, ref, { objective }))
|
||||
return settle(() => ctx.remote.goals.edit(sessionId, ref, { objective }))
|
||||
},
|
||||
onPause: async () => {
|
||||
const ref = refOf(sessionId)
|
||||
if (ref === undefined) return noCurrentGoal
|
||||
return settle(() => ctx.api.goals.pause(sessionId, ref))
|
||||
return settle(() => ctx.remote.goals.pause(sessionId, ref))
|
||||
},
|
||||
onResume: async () => {
|
||||
const ref = refOf(sessionId)
|
||||
if (ref === undefined) return noCurrentGoal
|
||||
return settle(() => ctx.api.goals.resume(sessionId, ref))
|
||||
return settle(() => ctx.remote.goals.resume(sessionId, ref))
|
||||
},
|
||||
onClear: async () => {
|
||||
const ref = refOf(sessionId)
|
||||
if (ref === undefined) return noCurrentGoal
|
||||
return settle(() => ctx.api.goals.clear(sessionId, ref))
|
||||
return settle(() => ctx.remote.goals.clear(sessionId, ref))
|
||||
},
|
||||
}),
|
||||
}, GoalDock))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* plugin fiber (HMR safety). The node half and the invariant companion are
|
||||
* exercised over the same Context.
|
||||
*/
|
||||
import { Context } from 'cordis'
|
||||
import { Context, Service } from 'cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, render } from '@testing-library/react'
|
||||
import { afterEach } from 'vitest'
|
||||
@@ -71,8 +71,17 @@ async function bench(options: {
|
||||
clear: answer(`${prefix}/clear`, ref),
|
||||
})
|
||||
let activeGoals: ReturnType<typeof goals> | undefined = goals('goals')
|
||||
ctx.provide('api', {
|
||||
get goals() { return activeGoals },
|
||||
class RemoteService extends Service {
|
||||
constructor(serviceCtx: Context) {
|
||||
super(serviceCtx, 'remote')
|
||||
}
|
||||
}
|
||||
new RemoteService(ctx)
|
||||
ctx.provide('remote.goals', {
|
||||
get edit() { return activeGoals?.edit },
|
||||
get pause() { return activeGoals?.pause },
|
||||
get resume() { return activeGoals?.resume },
|
||||
get clear() { return activeGoals?.clear },
|
||||
})
|
||||
await ctx.plugin(SlotsService).await()
|
||||
ctx.slots.register({
|
||||
|
||||
Reference in New Issue
Block a user