@deepseek-ai/dsh-client-test-runtime 0.0.1-rc.1 → 0.0.1-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -1,6 +1,6 @@
1
1
  # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
- # pnpm run verify-translation-pairing --write packages/client/test-runtime/README.md
5
- README.md: d5c0797c37168578f08a08f3d5d57670d7973db0
6
- README.zh.md: 57854213c3a9ea28850665eb26d07bc824c017e8
4
+ # pnpm run verify-translation-pairing --write packages/test-support/client-runtime/README.md
5
+ README.md: 15646d05daae0a39a36639f335a71d6d208da13b
6
+ README.zh.md: 9624bc8ffc997ba786f3cb1bba670344d44ac60d
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- jsdom slot test runtime for client feature specs: a real Cordis `Context`, the production `SlotsService` and web-react renderer, assembled around typed session/workspace doubles. Feature suites exercise declaration, registration, scope, store, inject, rendering, updates, and disposal without hand-building the machinery per suite — and without a second implementation of any production logic.
5
+ jsdom slot test runtime for client feature specs: a real Cordis `Context`, the production `SlotRegistry` and web-react renderer, assembled around typed session/workspace doubles. Feature suites exercise declaration, registration, scope, store, inject, rendering, updates, and disposal without hand-building the machinery per suite — and without a second implementation of any production logic.
6
6
 
7
- The doubles implement the same outward faces features receive through ctx (`TestSessions implements ISessions`, `TestWorkspaces implements IWorkspaces`; each fixture session is a `FixtureSession implements SessionFace`; `stubSettingsScope` is a `SettingsScope` with test-driven publications and a write spy), so a production face change breaks the bench at compile time instead of silently drifting. Provide-bundle materialization runs the production `SessionProvideChannel` — the one implementation shared with `SessionsService`. Fixtures feed plain data: list rows, conversation snapshots (immer-patched via `updateSnapshot`), projection values, and `ISession`-typed behavior stubs that fail loud when a spec calls an unstubbed verb. The typed `provide()` constrains fakes for declared service names to `Partial` of that service's outward face.
7
+ The doubles implement the same outward faces features receive through ctx (`TestSessions implements ISessions`, `TestWorkspaces implements IWorkspaces`; each fixture session is a `FixtureSession implements SessionFace`; `stubSettingsScope` is a `SettingsScope` with test-driven publications and a write spy), so a production face change breaks the bench at compile time instead of silently drifting. Provide-bundle materialization runs the production `SessionProvideChannel` — the one implementation shared with `SessionRuntime`. Fixtures feed plain data: list rows, conversation snapshots (immer-patched via `updateSnapshot`), projection values, and `ISession`-typed behavior stubs that fail loud when a spec calls an unstubbed verb. The typed `provide()` constrains fakes for declared service names to `Partial` of that service's outward face.
8
8
 
9
9
  Local DOM snapshots: `declare(children)` registers an auto frame whose per-key `<div data-slot>` wrappers are snapshot roots; `renderSlot(key, owner)` returns the slot-local view (container, scoped Testing Library queries, in-place `update(owner)`); a registered snapshot serializer folds CSS-module class hashes (`_frame_a1b2c3` → `frame`) to keep `.snap` files structural and collapses `<svg>` internals to a `data-content` fingerprint. Suites needing a custom page frame use `root.declare(children, Frame)` instead; `mount(plugin)` runs a real fiber with fail-loud service prechecks, and `dispose()` tears down views, feature fibers, minted scopes, and persisted store state on one axis.
10
10
 
package/README.zh.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 面向客户端功能测试的 jsdom slot 测试运行时:真实 Cordis `Context`、生产 `SlotsService` 与 web-react 渲染器,围绕带类型的 session/workspace 测试替身组装。功能套件无需逐套件手搭机器即可测遍声明、注册、scope、store、inject、渲染、更新与销毁——且不存在任何生产逻辑的第二份实现。
5
+ 面向客户端功能测试的 jsdom slot 测试运行时:真实 Cordis `Context`、生产 `SlotRegistry` 与 web-react 渲染器,围绕带类型的 session/workspace 测试替身组装。功能套件无需逐套件手搭机器即可测遍声明、注册、scope、store、inject、渲染、更新与销毁——且不存在任何生产逻辑的第二份实现。
6
6
 
7
- 替身实现的正是功能通过 ctx 获得的对外接口(`TestSessions implements ISessions`、`TestWorkspaces implements IWorkspaces`;每个 fixture session 是 `FixtureSession implements SessionFace`;`stubSettingsScope` 是发布由测试驱动、带写入 spy 的 `SettingsScope`),生产面一旦改形,测试台在编译期即断,而非静默漂移。provide bundle 材料化直接运行生产 `SessionProvideChannel`——与 `SessionsService` 共用同一份实现。fixture 灌入的是普通数据:列表行、会话快照(经 `updateSnapshot` 以 immer 补丁改写)、projection 值,以及按 `ISession` 取型的行为桩——spec 调用未打桩的动词时报错自明。带类型的 `provide()` 将已声明服务名的 fake 约束为该服务对外面的 `Partial` 子集。
7
+ 替身实现的正是功能通过 ctx 获得的对外接口(`TestSessions implements ISessions`、`TestWorkspaces implements IWorkspaces`;每个 fixture session 是 `FixtureSession implements SessionFace`;`stubSettingsScope` 是发布由测试驱动、带写入 spy 的 `SettingsScope`),生产面一旦改形,测试台在编译期即断,而非静默漂移。provide bundle 材料化直接运行生产 `SessionProvideChannel`——与 `SessionRuntime` 共用同一份实现。fixture 灌入的是普通数据:列表行、会话快照(经 `updateSnapshot` 以 immer 补丁改写)、projection 值,以及按 `ISession` 取型的行为桩——spec 调用未打桩的动词时报错自明。带类型的 `provide()` 将已声明服务名的 fake 约束为该服务对外面的 `Partial` 子集。
8
8
 
9
9
  局部 DOM 快照:`declare(children)` 注册自动 frame,逐 key 的 `<div data-slot>` 包裹层即快照根;`renderSlot(key, owner)` 返回该 slot 的局部视图(container、限定范围的 Testing Library 查询、原位 `update(owner)`);注册的快照序列化器把 CSS-module 哈希类名折回语义名(`_frame_a1b2c3` → `frame`)保持 `.snap` 只含结构,并把 `<svg>` 内部折叠为 `data-content` 指纹。需要自定义页面 frame 的套件改用 `root.declare(children, Frame)`;`mount(plugin)` 在真实 fiber 上运行并对缺失服务先行报错;`dispose()` 沿单一轴拆除视图、feature fiber、已铸 scope 与持久化 store 状态。
10
10
 
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Context, Inject } from "@deepseek-ai/cordis";
2
2
  import { Fragment, createElement, useSyncExternalStore } from "react";
3
3
  import { act, render, within } from "@testing-library/react";
4
- import { ConversationEventRegistry, ConversationViewRegistry, EMPTY_CHAT_SNAPSHOT, SessionProvideChannel, SlotsService, createScope, createSnapshotStore, scopeOf } from "@deepseek-ai/dsh-client-runtime/client";
4
+ import { ConversationEventRegistry, ConversationViewRegistry, EMPTY_CHAT_SNAPSHOT, EMPTY_CONVERSATION_VIEWS, SessionProvideChannel, SlotRegistry, createScope, createSnapshotStore, scopeOf } from "@deepseek-ai/dsh-client-runtime/client";
5
5
  import { createSlotRenderer } from "@deepseek-ai/dsh-client-web-react";
6
6
  import { afterEach, beforeEach, expect, vi } from "vitest";
7
7
  import { SESSION_SEARCH_RESULT_LIMIT } from "@deepseek-ai/dsh-host-apiproxy/api";
@@ -90,6 +90,7 @@ function registerDomSnapshotSerializer() {
90
90
  function conversationSnapshot(sessionId) {
91
91
  return {
92
92
  sessionId,
93
+ views: EMPTY_CONVERSATION_VIEWS,
93
94
  chat: EMPTY_CHAT_SNAPSHOT,
94
95
  nodes: [],
95
96
  turnTimings: /* @__PURE__ */ new Map(),
@@ -112,7 +113,7 @@ function conversationSnapshot(sessionId) {
112
113
  };
113
114
  }
114
115
  /**
115
- * A ready workspace list with no workspaces (the shape WorkspacesService
116
+ * A ready workspace list with no workspaces (the shape WorkspaceRuntime
116
117
  * projects after both baselines land).
117
118
  * @returns the initial state of the test workspaces store.
118
119
  */
@@ -261,9 +262,9 @@ var TestSessions = class {
261
262
  /** The useSessions standard feed (list rows + current selection). */
262
263
  list;
263
264
  /**
264
- * Atomic current-session provide projection (production SessionsService
265
+ * Atomic current-session provide projection (production SessionRuntime
265
266
  * mirror): selection changes and provider-roster changes publish through
266
- * this one source — the member the SlotsService host face hands the
267
+ * this one source — the member the SlotRegistry host face hands the
267
268
  * renderer's SessionProvider.
268
269
  */
269
270
  currentProvideInfo;
@@ -289,6 +290,7 @@ var TestSessions = class {
289
290
  current: void 0,
290
291
  phase: "ready",
291
292
  subagentsByParent: {},
293
+ jobsBySession: {},
292
294
  currentAddress: void 0
293
295
  });
294
296
  this.channel = new SessionProvideChannel({
@@ -385,7 +387,7 @@ var TestSessions = class {
385
387
  /**
386
388
  * Remove a session: list row, scope fiber, and per-session store instances
387
389
  * (with persisted state) die together — the same single lifecycle axis the
388
- * production SessionsService drives on session death, minus staging.
390
+ * production SessionRuntime drives on session death, minus staging.
389
391
  * @param id - session id.
390
392
  */
391
393
  async remove(id) {
@@ -820,6 +822,18 @@ var TestWorkspaces = class {
820
822
  await this.stubs.get("delete")?.(workspaceId);
821
823
  }
822
824
  /**
825
+ * Move a Workspace in display order (recorded; default no-op).
826
+ * @param workspaceId - Workspace to move.
827
+ * @param beforeWorkspaceId - Anchor; omitted appends.
828
+ */
829
+ async insertBefore(workspaceId, beforeWorkspaceId) {
830
+ this.calls.push({
831
+ method: "insertBefore",
832
+ args: [workspaceId, beforeWorkspaceId]
833
+ });
834
+ await this.stubs.get("insertBefore")?.(workspaceId, beforeWorkspaceId);
835
+ }
836
+ /**
823
837
  * Move an accounted session (recorded). The default echoes a minimal view.
824
838
  * @param workspaceId - target workspace.
825
839
  * @param sessionId - session to move.
@@ -876,12 +890,15 @@ function stubSettingsScope() {
876
890
  let snapshot = {
877
891
  status: "loading",
878
892
  value: void 0,
893
+ base: void 0,
894
+ user: void 0,
879
895
  revision: void 0,
880
896
  writable: false,
881
897
  mode: "host"
882
898
  };
883
899
  const listeners = /* @__PURE__ */ new Set();
884
900
  const set = vi.fn(() => Promise.resolve());
901
+ const unset = vi.fn(() => Promise.resolve());
885
902
  return {
886
903
  scope: {
887
904
  getSnapshot: () => snapshot,
@@ -891,9 +908,11 @@ function stubSettingsScope() {
891
908
  listeners.delete(listener);
892
909
  };
893
910
  },
894
- set
911
+ set,
912
+ unset
895
913
  },
896
914
  set,
915
+ unset,
897
916
  listenerCount: () => listeners.size,
898
917
  publish: (next) => {
899
918
  snapshot = {
@@ -905,10 +924,73 @@ function stubSettingsScope() {
905
924
  };
906
925
  }
907
926
  //#endregion
927
+ //#region lib/types/remote.js
928
+ /**
929
+ * Remote service test double for the forwarded-event path. Feature specs need
930
+ * `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
931
+ * host events to reach those subscribers, but not the generated namespaces or
932
+ * the wire — so this double implements subscription and dispatch only.
933
+ *
934
+ * Dispatch is driven the same way production drives it: `client/runtime` owns the
935
+ * host frame sink and hands each decoded `host/remote-event` frame to
936
+ * `$dispatch`. A spec therefore exercises its refresh chains by calling
937
+ * `$dispatch(name, args)` on this double.
938
+ *
939
+ * `$mount` rejects: a spec that reaches a generated namespace through this
940
+ * double has outgrown it and needs the real Client Remote service.
941
+ *
942
+ * One deliberate asymmetry with production: a throwing listener propagates out
943
+ * of the emit instead of being contained and logged, so a spec cannot lean on
944
+ * this double for the containment guarantee `$on` documents — assert that
945
+ * against the real service.
946
+ */
947
+ var TestRemote = class {
948
+ subscriptions = /* @__PURE__ */ new Map();
949
+ /**
950
+ * Register the double as `ctx.remote`.
951
+ * @param ctx - the spec's root Context.
952
+ */
953
+ constructor(ctx) {
954
+ ctx.provide("remote", this);
955
+ }
956
+ /**
957
+ * Deliver one forwarded host event to its subscribers, standing in for the
958
+ * carrier that owns the frame sink.
959
+ * @param event - forwarded host event name.
960
+ * @param args - the Host argument list, verbatim.
961
+ */
962
+ $dispatch(event, args) {
963
+ const listeners = this.subscriptions.get(event);
964
+ if (listeners === void 0) return;
965
+ for (const listener of [...listeners]) listener(...args);
966
+ }
967
+ /**
968
+ * Subscribe to one forwarded host event.
969
+ * @param event - forwarded host event name.
970
+ * @param listener - receives the Host argument list verbatim.
971
+ * @returns disposer removing this subscription.
972
+ */
973
+ $on(event, listener) {
974
+ const listeners = this.subscriptions.get(event) ?? /* @__PURE__ */ new Set();
975
+ this.subscriptions.set(event, listeners);
976
+ listeners.add(listener);
977
+ return () => {
978
+ listeners.delete(listener);
979
+ };
980
+ }
981
+ /**
982
+ * Generated-namespace mount, unsupported by this double.
983
+ * @returns never; always rejects.
984
+ */
985
+ $mount() {
986
+ return Promise.reject(/* @__PURE__ */ new Error("TestRemote: $mount needs the real Client Remote service"));
987
+ }
988
+ };
989
+ //#endregion
908
990
  //#region lib/types/translate.js
909
991
  /**
910
992
  * Test double of the locale lookup chain: a translate stub over plain
911
- * dictionaries, mirroring LocaleService's resolution order (first dictionary
993
+ * dictionaries, mirroring LocaleRuntime's resolution order (first dictionary
912
994
  * that owns the key wins, then the key itself stays visible) and its
913
995
  * `{name}` template interpolation. Specs stub the framework-injected `t`
914
996
  * seat with `makeTranslate(zh, commonZh)` instead of re-implementing the
@@ -938,7 +1020,7 @@ function makeTranslate(...dicts) {
938
1020
  //#region lib/types/locale-env.js
939
1021
  /**
940
1022
  * Browser-language pin for specs that assert localized copy. A fresh
941
- * LocaleService with no stored preference opens in the language `navigator`
1023
+ * LocaleRuntime with no stored preference opens in the language `navigator`
942
1024
  * asks for, and jsdom reports the runner's own (`en-US`) — so a spec asserting
943
1025
  * the product's Chinese copy states the browser it assumes instead of
944
1026
  * inheriting the machine's.
@@ -971,7 +1053,7 @@ function usePinnedBrowserLanguages(primary, ...rest) {
971
1053
  //#region lib/types/index.js
972
1054
  /**
973
1055
  * jsdom slot test runtime: a real small runtime — Cordis `Context`, the
974
- * runtime `SlotsService`, and the web-react renderer — assembled around
1056
+ * runtime `SlotRegistry`, and the web-react renderer — assembled around
975
1057
  * test-owned session/workspace doubles, so feature specs exercise
976
1058
  * declaration, registration, scope, store, inject, rendering, updates, and
977
1059
  * disposal without hand-building the machinery per suite.
@@ -1029,7 +1111,7 @@ var TestRoot = class {
1029
1111
  stabilize;
1030
1112
  disposeEntry;
1031
1113
  /**
1032
- * @param slots - the runtime SlotsService.
1114
+ * @param slots - the runtime SlotRegistry.
1033
1115
  * @param stabilize - the owning runtime's act wrapper.
1034
1116
  */
1035
1117
  constructor(slots, stabilize) {
@@ -1067,7 +1149,7 @@ var TestRoot = class {
1067
1149
  var SlotTestRuntime = class SlotTestRuntime {
1068
1150
  /** The runtime's Cordis root (escape hatch: extra services via `ctx.provide`, raw `ctx.plugin` mounts). */
1069
1151
  ctx;
1070
- /** The production SlotsService mounted on {@link SlotTestRuntime.ctx}. */
1152
+ /** The production SlotRegistry mounted on {@link SlotTestRuntime.ctx}. */
1071
1153
  slots;
1072
1154
  /** The test-owned 'root' occupant. */
1073
1155
  root;
@@ -1103,14 +1185,14 @@ var SlotTestRuntime = class SlotTestRuntime {
1103
1185
  } });
1104
1186
  }
1105
1187
  /**
1106
- * Assemble a runtime: real Context, mounted SlotsService, installed
1188
+ * Assemble a runtime: real Context, mounted SlotRegistry, installed
1107
1189
  * renderer, and the session/workspace doubles provided as services.
1108
1190
  * @returns the ready runtime.
1109
1191
  */
1110
1192
  static async create() {
1111
1193
  registerDomSnapshotSerializer();
1112
1194
  const ctx = new Context();
1113
- await ctx.plugin(SlotsService).await();
1195
+ await ctx.plugin(SlotRegistry).await();
1114
1196
  await ctx.plugin(ConversationEventRegistry).await();
1115
1197
  await ctx.plugin(ConversationViewRegistry).await();
1116
1198
  return new SlotTestRuntime(ctx, ctx.get("slots"));
@@ -1167,10 +1249,10 @@ var SlotTestRuntime = class SlotTestRuntime {
1167
1249
  /**
1168
1250
  * Declare child slots under an auto-generated root frame — the single-slot
1169
1251
  * mounting path for local DOM snapshots. Each key later supplied through
1170
- * {@link SlotTestRuntime.renderSlot} renders inside its own
1171
- * `<div data-slot="<key>">` wrapper (the snapshot root). Mutually exclusive
1172
- * with {@link TestRoot.declare} ('root' is a single slot); one call per
1173
- * runtime.
1252
+ * {@link SlotTestRuntime.renderSlot} renders inside the renderer's own
1253
+ * `<div data-slot="<key>">` outlet anchor (the snapshot root the frame
1254
+ * adds no wrapper of its own). Mutually exclusive with
1255
+ * {@link TestRoot.declare} ('root' is a single slot); one call per runtime.
1174
1256
  * @param children - child-slot declaration table (same contract as TestRoot.declare).
1175
1257
  * @returns completion of the act-wrapped registration.
1176
1258
  */
@@ -1179,10 +1261,7 @@ var SlotTestRuntime = class SlotTestRuntime {
1179
1261
  const cell = this.ownerCell;
1180
1262
  const AutoFrame = (props) => {
1181
1263
  useSyncExternalStore(cell.subscribe, cell.getVersion);
1182
- return createElement(Fragment, null, cell.entries().map(([key, owner]) => createElement("div", {
1183
- "data-slot": key,
1184
- key
1185
- }, props.renderSlot(key, owner))));
1264
+ return createElement(Fragment, null, cell.entries().map(([key, owner]) => createElement(Fragment, { key }, props.renderSlot(key, owner))));
1186
1265
  };
1187
1266
  await this.root.declare(children, AutoFrame);
1188
1267
  }
@@ -1256,4 +1335,4 @@ var SlotTestRuntime = class SlotTestRuntime {
1256
1335
  }
1257
1336
  };
1258
1337
  //#endregion
1259
- export { FixtureSession, SlotTestRuntime, TestRoot, TestSessions, TestWorkspaces, conversationSnapshot, domSnapshotSerializer, makeTranslate, registerDomSnapshotSerializer, stubSettingsScope, usePinnedBrowserLanguages, workspaceListState };
1338
+ export { FixtureSession, SlotTestRuntime, TestRemote, TestRoot, TestSessions, TestWorkspaces, conversationSnapshot, domSnapshotSerializer, makeTranslate, registerDomSnapshotSerializer, stubSettingsScope, usePinnedBrowserLanguages, workspaceListState };
package/lib/invariant.js CHANGED
@@ -10,7 +10,7 @@ const name = "client-test-runtime-invariant";
10
10
  const inject = ["invariants"];
11
11
  /**
12
12
  * No runtime invariant: this test-support package owns no production event
13
- * stream or mutable data — it assembles the runtime SlotsService and renderer
13
+ * stream or mutable data — it assembles the runtime SlotRegistry and renderer
14
14
  * (whose packages own their invariants) around test doubles; its own behavior
15
15
  * is exercised by its package tests.
16
16
  */
@@ -38,7 +38,7 @@ export interface SessionFixture {
38
38
  */
39
39
  export declare function conversationSnapshot(sessionId: SessionId): ConversationSnapshot;
40
40
  /**
41
- * A ready workspace list with no workspaces (the shape WorkspacesService
41
+ * A ready workspace list with no workspaces (the shape WorkspaceRuntime
42
42
  * projects after both baselines land).
43
43
  * @returns the initial state of the test workspaces store.
44
44
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * jsdom slot test runtime: a real small runtime — Cordis `Context`, the
3
- * runtime `SlotsService`, and the web-react renderer — assembled around
3
+ * runtime `SlotRegistry`, and the web-react renderer — assembled around
4
4
  * test-owned session/workspace doubles, so feature specs exercise
5
5
  * declaration, registration, scope, store, inject, rendering, updates, and
6
6
  * disposal without hand-building the machinery per suite.
@@ -15,7 +15,7 @@ import type { Fiber, Plugin } from '@deepseek-ai/cordis';
15
15
  import type { RenderResult } from '@testing-library/react';
16
16
  import type { queries } from '@testing-library/dom';
17
17
  import type { BoundFunctions } from '@testing-library/dom';
18
- import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client';
18
+ import { SlotRegistry } from '@deepseek-ai/dsh-client-runtime/client';
19
19
  import type { ChildrenDecl, ComposedProps, OwnerOf, SlotComponent, SlotMap, StoreInstanceLike } from '@deepseek-ai/dsh-client-ui-slots';
20
20
  import { TestSessions } from './sessions.ts';
21
21
  import { TestWorkspaces } from './workspaces.ts';
@@ -25,18 +25,20 @@ export { FixtureSession, TestSessions } from './sessions.ts';
25
25
  export { stubSettingsScope } from './settings-scope.ts';
26
26
  export type { StubSettingsScope } from './settings-scope.ts';
27
27
  export { TestWorkspaces } from './workspaces.ts';
28
+ export { TestRemote } from './remote.ts';
28
29
  export { conversationSnapshot, workspaceListState } from './fixtures.ts';
29
30
  export type { SessionBehaviorOverrides, SessionFixture, Stabilizer } from './fixtures.ts';
30
31
  export { makeTranslate } from './translate.ts';
31
32
  export { usePinnedBrowserLanguages } from './locale-env.ts';
32
33
  /**
33
34
  * One rendered slot's local view, from {@link SlotTestRuntime.renderSlot}:
34
- * the `data-slot` wrapper is the snapshot root (`expect(view.container)
35
- * .toMatchSnapshot()` captures exactly this slot's output), Testing Library
36
- * queries are bound inside it, and `update` re-renders with new owner props.
35
+ * the renderer's own `[data-slot]` outlet anchor is the snapshot root
36
+ * (`expect(view.container).toMatchSnapshot()` captures exactly this slot's
37
+ * output), Testing Library queries are bound inside it, and `update`
38
+ * re-renders with new owner props.
37
39
  */
38
40
  export interface SlotView<K extends keyof SlotMap & string> {
39
- /** The `<div data-slot="<key>">` wrapper around the slot's rendered output. */
41
+ /** The renderer's `<div data-slot="<key>">` anchor around the slot's rendered output. */
40
42
  readonly container: HTMLElement;
41
43
  /** Testing Library queries scoped to {@link SlotView.container}. */
42
44
  readonly view: BoundFunctions<typeof queries>;
@@ -71,10 +73,10 @@ export declare class TestRoot {
71
73
  private readonly stabilize;
72
74
  private disposeEntry;
73
75
  /**
74
- * @param slots - the runtime SlotsService.
76
+ * @param slots - the runtime SlotRegistry.
75
77
  * @param stabilize - the owning runtime's act wrapper.
76
78
  */
77
- constructor(slots: SlotsService, stabilize: Stabilizer);
79
+ constructor(slots: SlotRegistry, stabilize: Stabilizer);
78
80
  /**
79
81
  * Register the root frame, declaring (and thereby claiming) the child
80
82
  * slots. One declaration per runtime — a second call fails loud in the
@@ -96,8 +98,8 @@ export declare class TestRoot {
96
98
  export declare class SlotTestRuntime {
97
99
  /** The runtime's Cordis root (escape hatch: extra services via `ctx.provide`, raw `ctx.plugin` mounts). */
98
100
  readonly ctx: Context;
99
- /** The production SlotsService mounted on {@link SlotTestRuntime.ctx}. */
100
- readonly slots: SlotsService;
101
+ /** The production SlotRegistry mounted on {@link SlotTestRuntime.ctx}. */
102
+ readonly slots: SlotRegistry;
101
103
  /** The test-owned 'root' occupant. */
102
104
  readonly root: TestRoot;
103
105
  /** Sessions double (list/current observable, cells, scopes, behavior faces). */
@@ -115,7 +117,7 @@ export declare class SlotTestRuntime {
115
117
  private autoRootView;
116
118
  private constructor();
117
119
  /**
118
- * Assemble a runtime: real Context, mounted SlotsService, installed
120
+ * Assemble a runtime: real Context, mounted SlotRegistry, installed
119
121
  * renderer, and the session/workspace doubles provided as services.
120
122
  * @returns the ready runtime.
121
123
  */
@@ -148,10 +150,10 @@ export declare class SlotTestRuntime {
148
150
  /**
149
151
  * Declare child slots under an auto-generated root frame — the single-slot
150
152
  * mounting path for local DOM snapshots. Each key later supplied through
151
- * {@link SlotTestRuntime.renderSlot} renders inside its own
152
- * `<div data-slot="<key>">` wrapper (the snapshot root). Mutually exclusive
153
- * with {@link TestRoot.declare} ('root' is a single slot); one call per
154
- * runtime.
153
+ * {@link SlotTestRuntime.renderSlot} renders inside the renderer's own
154
+ * `<div data-slot="<key>">` outlet anchor (the snapshot root the frame
155
+ * adds no wrapper of its own). Mutually exclusive with
156
+ * {@link TestRoot.declare} ('root' is a single slot); one call per runtime.
155
157
  * @param children - child-slot declaration table (same contract as TestRoot.declare).
156
158
  * @returns completion of the act-wrapped registration.
157
159
  */
@@ -0,0 +1,49 @@
1
+ /** Test-owned Remote face: `$on` subscriptions driven by the internal forwarded-event plumbing. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ /**
4
+ * Remote service test double for the forwarded-event path. Feature specs need
5
+ * `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
6
+ * host events to reach those subscribers, but not the generated namespaces or
7
+ * the wire — so this double implements subscription and dispatch only.
8
+ *
9
+ * Dispatch is driven the same way production drives it: `client/runtime` owns the
10
+ * host frame sink and hands each decoded `host/remote-event` frame to
11
+ * `$dispatch`. A spec therefore exercises its refresh chains by calling
12
+ * `$dispatch(name, args)` on this double.
13
+ *
14
+ * `$mount` rejects: a spec that reaches a generated namespace through this
15
+ * double has outgrown it and needs the real Client Remote service.
16
+ *
17
+ * One deliberate asymmetry with production: a throwing listener propagates out
18
+ * of the emit instead of being contained and logged, so a spec cannot lean on
19
+ * this double for the containment guarantee `$on` documents — assert that
20
+ * against the real service.
21
+ */
22
+ export declare class TestRemote {
23
+ private readonly subscriptions;
24
+ /**
25
+ * Register the double as `ctx.remote`.
26
+ * @param ctx - the spec's root Context.
27
+ */
28
+ constructor(ctx: Context);
29
+ /**
30
+ * Deliver one forwarded host event to its subscribers, standing in for the
31
+ * carrier that owns the frame sink.
32
+ * @param event - forwarded host event name.
33
+ * @param args - the Host argument list, verbatim.
34
+ */
35
+ $dispatch(event: string, args: readonly unknown[]): void;
36
+ /**
37
+ * Subscribe to one forwarded host event.
38
+ * @param event - forwarded host event name.
39
+ * @param listener - receives the Host argument list verbatim.
40
+ * @returns disposer removing this subscription.
41
+ */
42
+ $on(event: string, listener: (...args: never[]) => void): () => void;
43
+ /**
44
+ * Generated-namespace mount, unsupported by this double.
45
+ * @returns never; always rejects.
46
+ */
47
+ $mount(): Promise<() => Promise<void>>;
48
+ }
49
+ //# sourceMappingURL=remote.d.ts.map
@@ -1,4 +1,4 @@
1
- /** Test-owned sessions face: the SlotsService host contract over declarative fixtures. */
1
+ /** Test-owned sessions face: the SlotRegistry host contract over declarative fixtures. */
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
3
  import type { AttachmentIdType } from '@deepseek-ai/dsh-attachment';
4
4
  import type { AgentContext, ConversationSnapshot, ISessions, ProjectionsFace, SessionFace, SessionId, SessionListState, SessionProvideDescriptor, SessionSearchResultItem, SessionSummary, SnapshotStore, SubagentAddress } from '@deepseek-ai/dsh-client-runtime/client';
@@ -95,9 +95,9 @@ export declare class TestSessions implements ISessions {
95
95
  /** The useSessions standard feed (list rows + current selection). */
96
96
  readonly list: SnapshotStore<SessionListState>;
97
97
  /**
98
- * Atomic current-session provide projection (production SessionsService
98
+ * Atomic current-session provide projection (production SessionRuntime
99
99
  * mirror): selection changes and provider-roster changes publish through
100
- * this one source — the member the SlotsService host face hands the
100
+ * this one source — the member the SlotRegistry host face hands the
101
101
  * renderer's SessionProvider.
102
102
  */
103
103
  readonly currentProvideInfo: HostObservable<SessionMaybeProvideInfo>;
@@ -149,7 +149,7 @@ export declare class TestSessions implements ISessions {
149
149
  /**
150
150
  * Remove a session: list row, scope fiber, and per-session store instances
151
151
  * (with persisted state) die together — the same single lifecycle axis the
152
- * production SessionsService drives on session death, minus staging.
152
+ * production SessionRuntime drives on session death, minus staging.
153
153
  * @param id - session id.
154
154
  */
155
155
  remove(id: string): Promise<void>;
@@ -7,6 +7,8 @@ export interface StubSettingsScope<T> {
7
7
  scope: SettingsScope<T>;
8
8
  /** Spy behind `scope.set`; resolves immediately. */
9
9
  set: ReturnType<typeof vi.fn>;
10
+ /** Spy behind `scope.unset`; resolves immediately. */
11
+ unset: ReturnType<typeof vi.fn>;
10
12
  /** @returns how many listeners are currently subscribed (disposal assertions). */
11
13
  listenerCount(): number;
12
14
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Test double of the locale lookup chain: a translate stub over plain
3
- * dictionaries, mirroring LocaleService's resolution order (first dictionary
3
+ * dictionaries, mirroring LocaleRuntime's resolution order (first dictionary
4
4
  * that owns the key wins, then the key itself stays visible) and its
5
5
  * `{name}` template interpolation. Specs stub the framework-injected `t`
6
6
  * seat with `makeTranslate(zh, commonZh)` instead of re-implementing the
@@ -91,6 +91,12 @@ export declare class TestWorkspaces implements IWorkspaces {
91
91
  * @param workspaceId - target workspace.
92
92
  */
93
93
  delete(workspaceId: WorkspaceId): Promise<void>;
94
+ /**
95
+ * Move a Workspace in display order (recorded; default no-op).
96
+ * @param workspaceId - Workspace to move.
97
+ * @param beforeWorkspaceId - Anchor; omitted appends.
98
+ */
99
+ insertBefore(workspaceId: WorkspaceId, beforeWorkspaceId?: WorkspaceId): Promise<void>;
94
100
  /**
95
101
  * Move an accounted session (recorded). The default echoes a minimal view.
96
102
  * @param workspaceId - target workspace.
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-test-runtime",
3
- "description": "jsdom slot test runtime: real Cordis Context + SlotsService + web-react renderer with test-owned session/workspace doubles for feature specs",
4
- "version": "0.0.1-rc.1",
3
+ "description": "jsdom slot test runtime: real Cordis Context + SlotRegistry + web-react renderer with test-owned session/workspace doubles for feature specs",
4
+ "version": "0.0.1-rc.5",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
- "directory": "packages/client/test-runtime"
11
+ "directory": "packages/test-support/client-runtime"
12
12
  },
13
13
  "type": "module",
14
14
  "main": "lib/index.js",
@@ -34,24 +34,24 @@
34
34
  "peerDependencies": {
35
35
  "react": "^18.2.0",
36
36
  "react-dom": "^18.2.0",
37
- "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.1",
38
- "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.1",
39
- "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.1",
40
- "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.1",
41
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
42
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1"
37
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.5",
38
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.5",
39
+ "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.5",
40
+ "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.5",
41
+ "@deepseek-ai/cordis": "^4.0.1-rc.4",
42
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.5"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/react": "~18.3.1",
46
46
  "@types/react-dom": "~18.3.0",
47
47
  "react": "^18.2.0",
48
48
  "react-dom": "^18.2.0",
49
- "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.1",
50
- "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.1",
51
- "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.1",
52
- "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.1",
53
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
54
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1"
49
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.5",
50
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.5",
51
+ "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.5",
52
+ "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.5",
53
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
54
+ "@deepseek-ai/cordis": "^4.0.1-rc.4"
55
55
  },
56
56
  "files": [
57
57
  "lib/index.js",