@monotykamary/dsh-client-web 0.1.0-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/web/README.md
5
+ README.md: 9a58cc2d453e7301d648342d8d9a0c4e8e9d2e83
6
+ README.zh.md: 050f98abda06cd1f8c9c632b1a1f508c64bcb3ff
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # @monotykamary/dsh-client-web
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Web shell kernel: `new AppWebEntry(el, seams?).run()` mounts the whole client through the two-stage boot (web2). Stage one (module face): build the client module system (`@monotykamary/dsh-client-modules`) over the host-pushed entry graph (`window.__DSH_BOOT__`) and prefetch the `immediately` tier in parallel — bundle execution registers factories only. Stage two (plugin face): mount the vendored cordis Loader with the module system injected through its `internal` contract, create one loader entry per graph row plus the shell-own app-shell assembly entry (tree.import materializes each module), and gate AppRoot on the settle (loader quiesced + every entry fiber ACTIVE → full UI in one switch). Composition is entirely the host graph's: the roster and the immediately tier live in the composing app; the shell makes zero composition decisions.
6
+
7
+ Shell self-sufficiency (web2 hard rule): the kernel value-imports no plugin package — the boot status store and signals are hand-rolled here (`loader-status.ts`), so the loading page works while (and especially when) plugins fail. The app-shell assembly (`@monotykamary/dsh-client-app-shell`, a shell-owned pseudo entry with no npm package behind it) is the only module registered through `registerStatic`; it inject-waits on slots/sessions/layout like any plugin.
8
+
9
+ `PLATFORM_MODULES` (src/platform.ts) is the single source of truth for shared modules: seed-table keys, tsdown client externals, and the Vite alias set are its projections.
10
+
11
+ The optional override parameter `seams` forwards the module system's `loadBundle` transport override (`BootSeams`) for environments where external `<script>` execution cannot reach the page context; ordinary browser callers omit it.
12
+
13
+ The shell owns browser-title projection. With a selected session carrying a durable title, it renders `<session title> — <existing HTML title>` and reacts to later title revisions; no selection or a selected untitled session preserves the existing title, and shell unmount restores it. The existing HTML title remains the configurable product suffix.
14
+
15
+ ## Model Experience
16
+
17
+ None, as the entry shell boots the browser plugin tree; nothing here reaches a model request.
18
+
19
+ #### KV Cache effect
20
+
21
+ None; this package neither assembles nor sends a provider request.
22
+
23
+ ## Known Limitations and Deferred Work
24
+
25
+ - **One-shot rendering by design** — the UI waits for the boot settle; a single entry failure keeps the loading page with a loud per-entry report, no partial availability (progressive rendering returns with its own project).
26
+ - **Narrow-window shell behavior lacks an assembled walkthrough** — ui-layout implements the concession chain, but this package has no shell-level narrow-viewport acceptance case.
package/README.zh.md ADDED
@@ -0,0 +1,26 @@
1
+ # @monotykamary/dsh-client-web
2
+
3
+ [English](README.md) | 中文
4
+
5
+ Web 外壳内核:`new AppWebEntry(el, seams?).run()` 通过两阶段启动(web2)挂载整个客户端。第一阶段(模块侧):构建客户端模块系统(`@monotykamary/dsh-client-modules`),以主机推送的配置项图(`window.__DSH_BOOT__`)为基础,并行预取 `immediately` 层级;执行组合包只会注册 factory。第二阶段(插件侧):挂载仓库内置的 Cordis Loader,并通过其 `internal` 约定注入模块系统;为每一行图数据创建一个 loader 配置项,另创建外壳自身的 app-shell 组装配置项(tree.import 会物化各模块);以 settle 作为 AppRoot 的门禁(loader 完全停稳 + 每个配置项 fiber 都为 ACTIVE → 一次切换显示完整 UI)。组合完全由主机图决定:花名册和 immediately 层级都位于负责组合的应用中;外壳不作任何组合决策。
6
+
7
+ 外壳自给自足(web2 硬性规则):内核不对任何插件包执行值导入;启动状态 store 与信号在这里手写(`loader-status.ts`),因此即使插件失败,加载页面仍能工作,而此时这一点尤其重要。app-shell 组装(`@monotykamary/dsh-client-app-shell`,由外壳拥有、背后没有 npm 包的伪配置项)是唯一通过 `registerStatic` 注册的模块;它与任何插件一样,通过 inject 等待 slots/sessions/layout。
8
+
9
+ `PLATFORM_MODULES`(src/platform.ts)是共享模块接口的唯一真源:种子表 key、tsdown 客户端 external 和 vite alias 集都是它的投影。
10
+
11
+ 可选的覆盖参数 `seams` 会为外部 `<script>` 执行无法到达页面上下文的环境转发模块系统的 `loadBundle` 传输覆盖(`BootSeams`);普通浏览器调用方省略此参数。
12
+
13
+ 外壳拥有浏览器标题投影。选中带有持久标题的会话时,它会渲染 `<session title> — <existing HTML title>` 并响应后续标题修订;未选择会话或选中无标题会话时,会保留现有标题;外壳卸载时恢复标题。现有 HTML 标题仍是可配置的产品后缀。
14
+
15
+ ## 模型体验
16
+
17
+ 无。入口外壳负责启动浏览器插件树;这里没有任何内容进入模型请求。
18
+
19
+ #### KV Cache 影响
20
+
21
+ 无;该包既不组装也不发送提供方请求。
22
+
23
+ ## 已知限制与暂缓事项
24
+
25
+ - **有意采用一次性渲染**:UI 等待启动 settle;只要一个配置项失败,加载页面就会保留并逐项显示醒目的报告,不提供部分可用性(渐进式渲染将作为独立项目恢复)。
26
+ - **窄窗口外壳行为缺少组装后演练**:ui-layout 已实现让步链,但该包没有外壳级窄视口验收用例。
package/lib/index.js ADDED
@@ -0,0 +1,436 @@
1
+ import * as ReactJsxRuntime from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as Cordis from "@monotykamary/cordis";
4
+ import { Context } from "@monotykamary/cordis";
5
+ import Loader from "@monotykamary/cordis-plugin-loader";
6
+ import * as ReactDomClient from "react-dom/client";
7
+ import { createRoot } from "react-dom/client";
8
+ import * as ModulesClient from "@monotykamary/dsh-client-modules/client";
9
+ import { ClientModuleSystem, parseBootManifest } from "@monotykamary/dsh-client-modules/client";
10
+ import * as WebReact from "@monotykamary/dsh-client-web-react";
11
+ import { bindSnapshotSelector, createSlotRenderer } from "@monotykamary/dsh-client-web-react";
12
+ import * as React from "react";
13
+ import { useEffect, useRef, useSyncExternalStore } from "react";
14
+ import * as ReactDom from "react-dom";
15
+ import * as UiSlots from "@monotykamary/dsh-client-ui-slots";
16
+ import * as UiPrimitives from "@monotykamary/dsh-client-ui-primitives";
17
+ import * as UiAttachment from "@monotykamary/dsh-client-ui-attachment";
18
+ import * as SchemaForm from "@monotykamary/dsh-client-schema-form";
19
+ //#region \0rolldown/runtime.js
20
+ var __defProp = Object.defineProperty;
21
+ var __exportAll = (all, no_symbols) => {
22
+ let target = {};
23
+ for (var name in all) __defProp(target, name, {
24
+ get: all[name],
25
+ enumerable: true
26
+ });
27
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
28
+ return target;
29
+ };
30
+ //#endregion
31
+ //#region lib/types/DocumentTitle.js
32
+ /**
33
+ * Project the selected durable session title into the browser title and
34
+ * restore the shell's original product title when unmounted.
35
+ * @param props - selected session title projection.
36
+ * @returns no rendered content.
37
+ */
38
+ function DocumentTitle({ title }) {
39
+ const original = useRef(document.title);
40
+ useEffect(() => {
41
+ document.title = title === void 0 ? original.current : `${title} — ${original.current}`;
42
+ return () => {
43
+ document.title = original.current;
44
+ };
45
+ }, [title]);
46
+ return null;
47
+ }
48
+ //#endregion
49
+ //#region lib/types/app.js
50
+ /**
51
+ * Build the renderApp factory the app-shell plugin provides to AppRoot.
52
+ * @param deps - assembly inputs.
53
+ * @returns factory producing the real UI tree (called once per AppRoot render after settled).
54
+ */
55
+ function buildRenderApp(deps) {
56
+ const { ctx } = deps;
57
+ const sessions = ctx.get("sessions");
58
+ if (sessions === void 0) throw new Error("shell assembly: sessions service unavailable");
59
+ const useSessions = bindSnapshotSelector(sessions.list);
60
+ const SessionDocumentTitle = () => {
61
+ const title = useSessions((state) => {
62
+ const id = state.current;
63
+ return id === void 0 ? void 0 : state.byId[id]?.title;
64
+ });
65
+ return jsx(DocumentTitle, { ...title === void 0 ? {} : { title } });
66
+ };
67
+ return () => jsxs(Fragment, { children: [jsx(SessionDocumentTitle, {}), ctx.slots.renderSlot("root", {})] });
68
+ }
69
+ //#endregion
70
+ //#region lib/types/app-shell.js
71
+ var app_shell_exports = /* @__PURE__ */ __exportAll({
72
+ APP_SHELL_ID: () => APP_SHELL_ID,
73
+ apply: () => apply,
74
+ inject: () => inject,
75
+ name: () => name
76
+ });
77
+ /** Shell-owned pseudo entry id under which the host graph mounts this plugin. */
78
+ const APP_SHELL_ID = "@monotykamary/dsh-client-app-shell";
79
+ /** Cordis plugin name. */
80
+ const name = "app-shell";
81
+ /** Services required before shell assembly. */
82
+ const inject = [
83
+ "slots",
84
+ "sessions",
85
+ "layout"
86
+ ];
87
+ /** Installs the React renderer and exposes the assembled application.
88
+ * @param ctx - Plugin context.
89
+ */
90
+ function apply(ctx) {
91
+ ctx.slots.install(createSlotRenderer());
92
+ let renderApp;
93
+ ctx.reflect.provide("appShell", { renderApp: () => {
94
+ renderApp ??= buildRenderApp({ ctx });
95
+ return renderApp();
96
+ } });
97
+ }
98
+ //#endregion
99
+ //#region \0dsh-css-stub:./AppRoot.module.css.mjs
100
+ var AppRoot_module_css_default = {};
101
+ //#endregion
102
+ //#region lib/types/AppRoot.js
103
+ /**
104
+ * Shell root: boot loading page → (boot settled) → real UI in one switch.
105
+ * Pure kernel component with zero plugin dependencies — before settled it may
106
+ * only rely on itself (the fail-loud presentation must not depend on the
107
+ * system whose failure it reports; the status/signal stores are kernel-own,
108
+ * shell self-sufficiency rule); the real UI is produced by the
109
+ * app-shell entry once every entry is active. A failed boot keeps the
110
+ * loading page, lists the per-entry fiber states and the sweep report (fail
111
+ * loud, no partial UI).
112
+ */
113
+ /** Boot gate: loading page until the boot settles; failures stay here. */
114
+ function AppRoot(props) {
115
+ const settled = useSyncExternalStore(props.settled.subscribe, props.settled.getSnapshot);
116
+ const status = useSyncExternalStore(props.status.subscribe, props.status.getSnapshot);
117
+ const error = useSyncExternalStore(props.error.subscribe, props.error.getSnapshot);
118
+ const failed = Object.entries(status).filter(([, s]) => s === "failed");
119
+ if (settled) return jsx(Fragment, { children: props.renderApp() });
120
+ const loud = error !== void 0 || failed.length > 0;
121
+ return jsx("div", {
122
+ className: AppRoot_module_css_default.boot,
123
+ children: jsxs("div", {
124
+ className: AppRoot_module_css_default.card,
125
+ children: [jsx("div", {
126
+ className: AppRoot_module_css_default.wordmark,
127
+ children: "HARNESS"
128
+ }), !loud ? jsxs(Fragment, { children: [jsx("div", { className: AppRoot_module_css_default.spinner }), jsx("div", {
129
+ className: AppRoot_module_css_default.hint,
130
+ children: "Loading plugins…"
131
+ })] }) : jsxs("div", {
132
+ className: AppRoot_module_css_default.failed,
133
+ children: [
134
+ jsx("div", {
135
+ className: AppRoot_module_css_default.failedTitle,
136
+ children: "Failed to load plugins"
137
+ }),
138
+ failed.map(([id]) => jsx("div", {
139
+ className: AppRoot_module_css_default.failedItem,
140
+ children: id
141
+ }, id)),
142
+ error !== void 0 && jsx("div", {
143
+ className: AppRoot_module_css_default.failedItem,
144
+ children: error
145
+ })
146
+ ]
147
+ })]
148
+ })
149
+ });
150
+ }
151
+ //#endregion
152
+ //#region lib/types/seed.js
153
+ /**
154
+ * Platform-singleton module-table. These are the ONLY entities the shell
155
+ * shares into the frozen module table — fetch bundles resolve their externals
156
+ * against exactly this set through the loader's require. Keys come from the
157
+ * platform constant module ({@link ./platform.ts}, the single source
158
+ * of truth with the tsdown client externals); values stay shell-static
159
+ * imports so every bundle sees the same instance.
160
+ */
161
+ /**
162
+ * Build the static table handed to the module loader at boot.
163
+ * @returns module specifier → exported entity (one entry per platform word).
164
+ */
165
+ function getStaticModules() {
166
+ return {
167
+ "react": React,
168
+ "react/jsx-runtime": ReactJsxRuntime,
169
+ "react-dom": ReactDom,
170
+ "react-dom/client": ReactDomClient,
171
+ "@monotykamary/cordis": Cordis,
172
+ "@monotykamary/dsh-client-ui-slots": UiSlots,
173
+ "@monotykamary/dsh-client-web-react": WebReact,
174
+ "@monotykamary/dsh-client-ui-primitives": UiPrimitives,
175
+ "@monotykamary/dsh-client-ui-attachment": UiAttachment,
176
+ "@monotykamary/dsh-client-schema-form": SchemaForm
177
+ };
178
+ }
179
+ //#endregion
180
+ //#region lib/types/loader-status.js
181
+ /**
182
+ * Value mirror of cordis's `FiberState` const enum: a const enum has no
183
+ * runtime object to import (and esbuild-based pipelines cannot inline it
184
+ * across modules), so these values mirror the pinned vendored definition
185
+ * while retaining its type (same rationale as dsh-tool-cordis's mirror).
186
+ */
187
+ const FIBER_STATE = {
188
+ PENDING: 0,
189
+ LOADING: 1,
190
+ ACTIVE: 2,
191
+ FAILED: 3,
192
+ DISPOSED: 4,
193
+ UNLOADING: 5
194
+ };
195
+ /** Label for each fiber state, keyed by member (inlining-safe — no reverse mapping). */
196
+ const STATE_LABELS = {
197
+ [FIBER_STATE.PENDING]: "pending",
198
+ [FIBER_STATE.LOADING]: "loading",
199
+ [FIBER_STATE.ACTIVE]: "active",
200
+ [FIBER_STATE.FAILED]: "failed",
201
+ [FIBER_STATE.DISPOSED]: "disposed",
202
+ [FIBER_STATE.UNLOADING]: "unloading"
203
+ };
204
+ /**
205
+ * Create a writable kernel signal.
206
+ * @param init - initial value.
207
+ * @returns the signal.
208
+ */
209
+ function createSignal(init) {
210
+ let value = init;
211
+ const listeners = /* @__PURE__ */ new Set();
212
+ return {
213
+ getSnapshot: () => value,
214
+ subscribe: (fn) => {
215
+ listeners.add(fn);
216
+ return () => {
217
+ listeners.delete(fn);
218
+ };
219
+ },
220
+ set: (next) => {
221
+ value = next;
222
+ for (const fn of [...listeners]) fn();
223
+ }
224
+ };
225
+ }
226
+ /**
227
+ * Create the boot status store.
228
+ * @returns the store (empty until the boot chain projects rows).
229
+ */
230
+ function createLoaderStatusStore() {
231
+ let value = {};
232
+ const listeners = /* @__PURE__ */ new Set();
233
+ return {
234
+ getSnapshot: () => value,
235
+ subscribe: (fn) => {
236
+ listeners.add(fn);
237
+ return () => {
238
+ listeners.delete(fn);
239
+ };
240
+ },
241
+ set: (id, state) => {
242
+ value = {
243
+ ...value,
244
+ [id]: state
245
+ };
246
+ for (const fn of [...listeners]) fn();
247
+ }
248
+ };
249
+ }
250
+ //#endregion
251
+ //#region lib/types/boot.js
252
+ /**
253
+ * Web shell boot kernel — the face consumed by the apps/web entry. Everything
254
+ * here is machinery that cannot itself be a loader entry, and none of it
255
+ * value-imports a plugin package (shell self-sufficiency rule: the
256
+ * loading page must work while — especially when — plugins fail). The one
257
+ * sanctioned exception is the modules package (bootstrap
258
+ * identity): the module system cannot arrive through itself, so its class
259
+ * and its client-half wrapper are shell-bundled and the kernel adopts its
260
+ * plugin entry once cordis is up.
261
+ *
262
+ * AppWebEntry.run(), module face first, then plugin face: parse
263
+ * `window.__DSH_BOOT__` into the two-view BootManifest (wire boundary)
264
+ * → build the module system over the module-view rows → render the loading
265
+ * page → prefetch every `immediately` row in parallel with mounting the
266
+ * vendored cordis Loader (`internal` contract injection BEFORE any entry exists —
267
+ * the bare-import fallback in tree.import must never run in a browser) →
268
+ * await the prefetch tier, THEN adopt the modules entry and create one
269
+ * loader entry per plugin-view row plus the shell-own app-shell assembly
270
+ * entry → loader.await() + a full fiber sweep (all ACTIVE, else fail
271
+ * listing who/what/which service) → flip the settled signal so AppRoot
272
+ * switches to the real UI in one pass.
273
+ *
274
+ * Entry creation waits for the whole immediately tier: materialization runs
275
+ * synchronous cross-package require edges (e.g. locale → runtime/client) that
276
+ * fiber inject waiting cannot protect — a bundle's factory must be
277
+ * registered before any dependent entry materializes. Per-row prefetch
278
+ * failures still resolve silently (the create-side import reloads and
279
+ * owns the loud failure), so the barrier never turns one bad bundle into a
280
+ * boot-wide fail-fast.
281
+ *
282
+ * Composition lives in the host graph; the shell makes zero composition
283
+ * decisions (the app-shell assembly is itself a graph entry, the only
284
+ * shell-own module registered with the module system).
285
+ */
286
+ /**
287
+ * The modules package's own graph row id. The kernel adopts that entry
288
+ * itself (its wrapper is statically registered — shell-bundled code, never
289
+ * fetched), so the plugin-row loop must skip it: the vendored Group.create
290
+ * does not deduplicate by name, and a second fiber would provide 'modules'
291
+ * twice.
292
+ */
293
+ const MODULES_ID = "@monotykamary/dsh-client-modules";
294
+ /**
295
+ * The web shell kernel: mounts the loading page into a DOM element and runs
296
+ * the two-stage boot over the host graph. Fields hold only what must exist
297
+ * before cordis does — the parsed manifest, the module system, and the
298
+ * loading-page UI handles; everything else lives in plugins.
299
+ */
300
+ var AppWebEntry = class {
301
+ el;
302
+ seams;
303
+ status = createLoaderStatusStore();
304
+ settled = createSignal(false);
305
+ error = createSignal(void 0);
306
+ ctx;
307
+ modules;
308
+ manifest;
309
+ root;
310
+ /**
311
+ * Hold the mount point; all work happens in {@link run}.
312
+ * @param el - mount point (the app's #root).
313
+ * @param seams - Optional module transport overrides for test environments.
314
+ */
315
+ constructor(el, seams) {
316
+ this.el = el;
317
+ this.seams = seams;
318
+ }
319
+ /**
320
+ * Run the boot chain to settlement. Boot-chain failures resolve (not
321
+ * reject): the loading page stays up and renders the failure report (the
322
+ * fail-loud surface the kernel owns). Rejects only when the boot manifest
323
+ * is missing or malformed — there is nothing to boot against.
324
+ * @returns resolves once the UI settled or the failure report rendered.
325
+ */
326
+ async run() {
327
+ this.manifest = parseBootManifest(globalThis.__DSH_BOOT__);
328
+ this.modules = new ClientModuleSystem({
329
+ modules: this.manifest.modules,
330
+ staticModules: getStaticModules(),
331
+ ...this.seams
332
+ });
333
+ this.modules.registerStatic(APP_SHELL_ID, app_shell_exports);
334
+ this.modules.registerStatic(MODULES_ID, ModulesClient);
335
+ globalThis.__DSH_MODULES__ = this.modules;
336
+ this.root = createRoot(this.el);
337
+ this.root.render(jsx(AppRoot, {
338
+ settled: this.settled,
339
+ status: this.status,
340
+ error: this.error,
341
+ renderApp: () => {
342
+ const shell = this.ctx.get("appShell");
343
+ if (shell === void 0) throw new Error("web boot: appShell service missing after settled");
344
+ return shell.renderApp();
345
+ }
346
+ }));
347
+ const prefetching = this.prefetchImmediateTier();
348
+ this.ctx = new Context();
349
+ try {
350
+ await this.runPluginBoot(prefetching);
351
+ this.settled.set(true);
352
+ } catch (reason) {
353
+ console.error(reason);
354
+ this.error.set(reason instanceof Error ? reason.message : String(reason));
355
+ }
356
+ }
357
+ /** Unmount the shell (loading page or settled UI). */
358
+ dispose() {
359
+ this.root?.unmount();
360
+ }
361
+ /** Prefetch the immediately tier (factory registration only; failures defer to the import path). */
362
+ async prefetchImmediateTier() {
363
+ await Promise.all(this.manifest.plugins.filter((row) => row.immediately).map((row) => this.modules.prefetch(row.id).catch(() => {})));
364
+ }
365
+ /** Plugin face: mount the Loader, inject the `internal` contract, adopt modules, create the graph entries, settle, sweep. */
366
+ async runPluginBoot(prefetching) {
367
+ const ctx = this.ctx;
368
+ await ctx.plugin(Loader);
369
+ const loader = ctx.loader;
370
+ loader.internal = this.modules;
371
+ ctx.on("internal/status", (fiber) => {
372
+ const entry = fiber.entry;
373
+ if (entry === void 0 || entry.fiber === void 0) return;
374
+ this.status.set(entry.options.name, STATE_LABELS[entry.fiber.state]);
375
+ });
376
+ await prefetching;
377
+ const rows = [
378
+ MODULES_ID,
379
+ ...this.manifest.plugins.map((row) => row.id).filter((id) => id !== MODULES_ID),
380
+ APP_SHELL_ID
381
+ ];
382
+ await Promise.all(rows.map(async (name) => {
383
+ this.status.set(name, "loading");
384
+ const id = await loader.create({ name });
385
+ if (loader.resolve(id).fiber === void 0) this.status.set(name, "failed");
386
+ }));
387
+ await loader.await();
388
+ this.assertEntriesActive();
389
+ }
390
+ /**
391
+ * Sweep every loader entry after the tree quiesced: an entry without a
392
+ * fiber failed its import; a fiber not ACTIVE is FAILED (apply threw) or
393
+ * PENDING (a required service never arrived — cordis inject waiting has no
394
+ * timeout, so this sweep is the fail-loud compensation).
395
+ */
396
+ assertEntriesActive() {
397
+ const ctx = this.ctx;
398
+ const failures = [];
399
+ for (const entry of ctx.loader.entries()) {
400
+ const name = entry.options.name;
401
+ if (entry.fiber === void 0) {
402
+ failures.push(`${name}: import failed (see console for the import error)`);
403
+ continue;
404
+ }
405
+ const state = STATE_LABELS[entry.fiber.state];
406
+ if (state === "active") continue;
407
+ if (state === "pending") {
408
+ const missing = Object.keys(entry.fiber.inject).filter((service) => ctx.get(service) === void 0);
409
+ failures.push(`${name}: pending (waiting for service${missing.length === 1 ? "" : "s"}: ${missing.join(", ") || "unknown"})`);
410
+ } else failures.push(`${name}: ${state}`);
411
+ }
412
+ if (failures.length > 0) throw new Error(`web boot: ${String(failures.length)} entr${failures.length === 1 ? "y" : "ies"} did not activate\n${failures.join("\n")}`);
413
+ }
414
+ };
415
+ //#endregion
416
+ //#region lib/types/platform.js
417
+ /**
418
+ * Shared browser platform modules. Seeding, bundling externals, and Vite
419
+ * aliases consume this list so their module identities cannot drift.
420
+ * @module @monotykamary/dsh-client-web/src/platform
421
+ */
422
+ /** The module specifiers the shell shares into the frozen module table. */
423
+ const PLATFORM_MODULES = [
424
+ "react",
425
+ "react/jsx-runtime",
426
+ "react-dom",
427
+ "react-dom/client",
428
+ "@monotykamary/cordis",
429
+ "@monotykamary/dsh-client-ui-slots",
430
+ "@monotykamary/dsh-client-web-react",
431
+ "@monotykamary/dsh-client-ui-primitives",
432
+ "@monotykamary/dsh-client-ui-attachment",
433
+ "@monotykamary/dsh-client-schema-form"
434
+ ];
435
+ //#endregion
436
+ export { APP_SHELL_ID, AppRoot, AppWebEntry, DocumentTitle, FIBER_STATE, PLATFORM_MODULES, STATE_LABELS, buildRenderApp, createLoaderStatusStore, createSignal, getStaticModules };
@@ -0,0 +1,25 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@monotykamary/dsh-client-web`.
4
+ * @module @monotykamary/dsh-client-web/invariant
5
+ */
6
+ const PACKAGE_NAME = "@monotykamary/dsh-client-web";
7
+ /** Cordis companion plugin name. */
8
+ const name = "client-web-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the vite entry shell — boot glue and module-table
13
+ * seeding with no cordis events and no cross-plugin mutable state; the boot
14
+ * chain (loading page → settled → one-flip UI) is asserted by the web smoke
15
+ * e2e against the real carrier.
16
+ */
17
+ const install = () => {};
18
+ /**
19
+ * Register this package's invariant companion.
20
+ * @param ctx - Cordis context carrying the invariant service.
21
+ * @returns the installed registration's disposer after setup succeeds.
22
+ */
23
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
24
+ //#endregion
25
+ export { apply, inject, name };
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { KernelSignal, LoaderStatus } from './loader-status.ts';
3
+ /** AppRoot props: settled signal, fiber-state projection feed, boot failure report, deferred real-UI factory. */
4
+ export interface AppRootProps {
5
+ /** True once the boot chain settled (loader quiesced + all entries ACTIVE); the boot closure flips it. */
6
+ settled: KernelSignal<boolean>;
7
+ /** Per-entry fiber-state projection store (drives loading/failed rendering). */
8
+ status: KernelSignal<LoaderStatus>;
9
+ /** Boot failure report (the settle rejection message); undefined while loading or after success. */
10
+ error: KernelSignal<string | undefined>;
11
+ /** Builds the real UI; called only after settled. */
12
+ renderApp: () => ReactNode;
13
+ }
14
+ /** Boot gate: loading page until the boot settles; failures stay here. */
15
+ export declare function AppRoot(props: AppRootProps): import("react").JSX.Element;
16
+ //# sourceMappingURL=AppRoot.d.ts.map
@@ -0,0 +1,13 @@
1
+ /** Props for the shell-owned browser title projection. */
2
+ export interface DocumentTitleProps {
3
+ /** Durable title of the selected session, or undefined for the product title. */
4
+ title?: string;
5
+ }
6
+ /**
7
+ * Project the selected durable session title into the browser title and
8
+ * restore the shell's original product title when unmounted.
9
+ * @param props - selected session title projection.
10
+ * @returns no rendered content.
11
+ */
12
+ export declare function DocumentTitle({ title }: DocumentTitleProps): null;
13
+ //# sourceMappingURL=DocumentTitle.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * App-shell assembly plugin. Its pseudo package id exists only in the host
3
+ * graph and shell registry; there is no npm package behind it.
4
+ */
5
+ import type { ReactNode } from 'react';
6
+ import type { Context } from '@monotykamary/cordis';
7
+ /** Shell-owned pseudo entry id under which the host graph mounts this plugin. */
8
+ export declare const APP_SHELL_ID = "@monotykamary/dsh-client-app-shell";
9
+ /** The assembled-UI face AppRoot renders once the boot settles. */
10
+ export interface AppShellService {
11
+ /** Build (once) and render the real UI tree. */
12
+ renderApp: () => ReactNode;
13
+ }
14
+ declare module '@monotykamary/cordis' {
15
+ interface Context {
16
+ /** The shell assembly face, provided by the app-shell entry once its inject set is active. */
17
+ appShell: AppShellService;
18
+ }
19
+ }
20
+ /** Cordis plugin name. */
21
+ export declare const name = "app-shell";
22
+ /** Services required before shell assembly. */
23
+ export declare const inject: string[];
24
+ /** Installs the React renderer and exposes the assembled application.
25
+ * @param ctx - Plugin context.
26
+ */
27
+ export declare function apply(ctx: Context): void;
28
+ //# sourceMappingURL=app-shell.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Real-UI assembly closure, invoked by the app-shell plugin once its inject
3
+ * set is active: the whole layout tree hangs off the built-in 'root' slot
4
+ * (ui-layout registers AppFrame there and renders the child slots
5
+ * internally) — the shell's render is the one ctx-level renderSlot call in
6
+ * the program.
7
+ */
8
+ import type { ReactNode } from 'react';
9
+ import type { Context } from '@monotykamary/cordis';
10
+ /** Assembly inputs: the active app-shell plugin ctx (slots/sessions/layout services provided). */
11
+ export interface AssemblyDeps {
12
+ /** Client context with the assembly's inject set active. */
13
+ ctx: Context;
14
+ }
15
+ /**
16
+ * Build the renderApp factory the app-shell plugin provides to AppRoot.
17
+ * @param deps - assembly inputs.
18
+ * @returns factory producing the real UI tree (called once per AppRoot render after settled).
19
+ */
20
+ export declare function buildRenderApp(deps: AssemblyDeps): () => ReactNode;
21
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { type ClientModuleSystemOptions } from '@monotykamary/dsh-client-modules/client';
2
+ import './base.css';
3
+ /** Module transport hook the shell passes through (jsdom tests replace the <script> path). */
4
+ export type BootSeams = Pick<ClientModuleSystemOptions, 'loadBundle'>;
5
+ /**
6
+ * The web shell kernel: mounts the loading page into a DOM element and runs
7
+ * the two-stage boot over the host graph. Fields hold only what must exist
8
+ * before cordis does — the parsed manifest, the module system, and the
9
+ * loading-page UI handles; everything else lives in plugins.
10
+ */
11
+ export declare class AppWebEntry {
12
+ private readonly el;
13
+ private readonly seams;
14
+ private readonly status;
15
+ private readonly settled;
16
+ private readonly error;
17
+ private ctx;
18
+ private modules;
19
+ private manifest;
20
+ private root;
21
+ /**
22
+ * Hold the mount point; all work happens in {@link run}.
23
+ * @param el - mount point (the app's #root).
24
+ * @param seams - Optional module transport overrides for test environments.
25
+ */
26
+ constructor(el: HTMLElement, seams?: BootSeams);
27
+ /**
28
+ * Run the boot chain to settlement. Boot-chain failures resolve (not
29
+ * reject): the loading page stays up and renders the failure report (the
30
+ * fail-loud surface the kernel owns). Rejects only when the boot manifest
31
+ * is missing or malformed — there is nothing to boot against.
32
+ * @returns resolves once the UI settled or the failure report rendered.
33
+ */
34
+ run(): Promise<void>;
35
+ /** Unmount the shell (loading page or settled UI). */
36
+ dispose(): void;
37
+ /** Prefetch the immediately tier (factory registration only; failures defer to the import path). */
38
+ private prefetchImmediateTier;
39
+ /** Plugin face: mount the Loader, inject the `internal` contract, adopt modules, create the graph entries, settle, sweep. */
40
+ private runPluginBoot;
41
+ /**
42
+ * Sweep every loader entry after the tree quiesced: an entry without a
43
+ * fiber failed its import; a fiber not ACTIVE is FAILED (apply threw) or
44
+ * PENDING (a required service never arrived — cordis inject waiting has no
45
+ * timeout, so this sweep is the fail-loud compensation).
46
+ */
47
+ private assertEntriesActive;
48
+ }
49
+ //# sourceMappingURL=boot.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Web shell library entry. The shell's product is {@link AppWebEntry} —
3
+ * apps/web's vite entry runs it against #root; everything else (AppRoot
4
+ * gate, app-shell assembly entry, module-table staticModules, platform constants) is
5
+ * internal to the boot chain. PLATFORM_MODULES is re-exported as the
6
+ * single source of truth for the tsdown client externals projection.
7
+ * @module @monotykamary/dsh-client-web
8
+ */
9
+ export { AppWebEntry, type BootSeams } from './boot.tsx';
10
+ export { AppRoot, type AppRootProps } from './AppRoot.tsx';
11
+ export { buildRenderApp, type AssemblyDeps } from './app.tsx';
12
+ export { DocumentTitle, type DocumentTitleProps } from './DocumentTitle.tsx';
13
+ export { APP_SHELL_ID, type AppShellService } from './app-shell.ts';
14
+ export { getStaticModules } from './seed.ts';
15
+ export { PLATFORM_MODULES, type PlatformModule } from './platform.ts';
16
+ export { STATE_LABELS, FIBER_STATE, createSignal, createLoaderStatusStore, type LoaderStatus, type LoaderEntryState, type KernelSignal, type KernelValueSignal, type LoaderStatusStore, } from './loader-status.ts';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@monotykamary/dsh-client-web`.
3
+ * @module @monotykamary/dsh-client-web/invariant
4
+ */
5
+ import type { Context } from '@monotykamary/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "client-web-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Fiber-state projection vocabulary and the kernel-owned status store for the
3
+ * boot loading page. The status AppRoot renders is a projection of the real
4
+ * cordis fiber states (display the truth, not a retelling) — the boot chain
5
+ * subscribes `internal/status` and recomputes one row per loader entry.
6
+ *
7
+ * The store is hand-rolled here because of the shell self-sufficiency rule:
8
+ * the snapshot-store machinery lives in the runtime PLUGIN
9
+ * package, and the shell kernel must not value-import any plugin package —
10
+ * the loading page has to work while (and especially when) plugins fail.
11
+ * @module @monotykamary/dsh-client-web/src/loader-status
12
+ */
13
+ import type { FiberState } from '@monotykamary/cordis';
14
+ /**
15
+ * Value mirror of cordis's `FiberState` const enum: a const enum has no
16
+ * runtime object to import (and esbuild-based pipelines cannot inline it
17
+ * across modules), so these values mirror the pinned vendored definition
18
+ * while retaining its type (same rationale as dsh-tool-cordis's mirror).
19
+ */
20
+ export declare const FIBER_STATE: {
21
+ readonly PENDING: FiberState.PENDING;
22
+ readonly LOADING: FiberState.LOADING;
23
+ readonly ACTIVE: FiberState.ACTIVE;
24
+ readonly FAILED: FiberState.FAILED;
25
+ readonly DISPOSED: FiberState.DISPOSED;
26
+ readonly UNLOADING: FiberState.UNLOADING;
27
+ };
28
+ /** One entry's projected state label (lower-case face of {@link FiberState}). */
29
+ export type LoaderEntryState = 'pending' | 'loading' | 'active' | 'failed' | 'disposed' | 'unloading';
30
+ /** Label for each fiber state, keyed by member (inlining-safe — no reverse mapping). */
31
+ export declare const STATE_LABELS: Record<FiberState, LoaderEntryState>;
32
+ /** Per-entry state projection (AppRoot's status feed), keyed by entry name. */
33
+ export type LoaderStatus = Record<string, LoaderEntryState>;
34
+ /** Minimal observable snapshot the kernel components consume (useSyncExternalStore shape). */
35
+ export interface KernelSignal<T> {
36
+ /** Current value (stable reference between changes). */
37
+ getSnapshot: () => T;
38
+ /**
39
+ * Subscribe to changes.
40
+ * @param fn - change listener.
41
+ * @returns the unsubscribe disposer.
42
+ */
43
+ subscribe: (fn: () => void) => () => void;
44
+ }
45
+ /** Writable one-value signal (settled flag, boot failure report). */
46
+ export interface KernelValueSignal<T> extends KernelSignal<T> {
47
+ /**
48
+ * Publish a new value and notify subscribers.
49
+ * @param next - the new value.
50
+ */
51
+ set: (next: T) => void;
52
+ }
53
+ /**
54
+ * Create a writable kernel signal.
55
+ * @param init - initial value.
56
+ * @returns the signal.
57
+ */
58
+ export declare function createSignal<T>(init: T): KernelValueSignal<T>;
59
+ /** The boot status store: per-entry rows over a {@link KernelSignal} face. */
60
+ export interface LoaderStatusStore extends KernelSignal<LoaderStatus> {
61
+ /**
62
+ * Project one entry's state (copy-on-write so getSnapshot references only
63
+ * change on writes — useSyncExternalStore contract).
64
+ * @param id - entry name.
65
+ * @param state - projected fiber state.
66
+ */
67
+ set: (id: string, state: LoaderEntryState) => void;
68
+ }
69
+ /**
70
+ * Create the boot status store.
71
+ * @returns the store (empty until the boot chain projects rows).
72
+ */
73
+ export declare function createLoaderStatusStore(): LoaderStatusStore;
74
+ //# sourceMappingURL=loader-status.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared browser platform modules. Seeding, bundling externals, and Vite
3
+ * aliases consume this list so their module identities cannot drift.
4
+ * @module @monotykamary/dsh-client-web/src/platform
5
+ */
6
+ /** The module specifiers the shell shares into the frozen module table. */
7
+ export declare const PLATFORM_MODULES: readonly ["react", "react/jsx-runtime", "react-dom", "react-dom/client", "@monotykamary/cordis", "@monotykamary/dsh-client-ui-slots", "@monotykamary/dsh-client-web-react", "@monotykamary/dsh-client-ui-primitives", "@monotykamary/dsh-client-ui-attachment", "@monotykamary/dsh-client-schema-form"];
8
+ /** One platform module specifier (a seed-table key). */
9
+ export type PlatformModule = (typeof PLATFORM_MODULES)[number];
10
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Build the static table handed to the module loader at boot.
3
+ * @returns module specifier → exported entity (one entry per platform word).
4
+ */
5
+ export declare function getStaticModules(): Record<string, unknown>;
6
+ //# sourceMappingURL=seed.d.ts.map
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@monotykamary/dsh-client-web",
3
+ "description": "Web shell kernel: bootWebShell (module system holding + seed table + two-stage boot + AppRoot gate + app-shell assembly entry), consumed by the apps/web vite entry",
4
+ "version": "0.1.0-rc.5",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/client/web"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "react": "^18.2.0",
31
+ "react-dom": "^18.2.0",
32
+ "@monotykamary/dsh-client-schema-form": "^0.1.0-rc.5",
33
+ "@monotykamary/dsh-client-ui-attachment": "^0.1.0-rc.5",
34
+ "@monotykamary/dsh-client-ui-primitives": "^0.1.0-rc.5",
35
+ "@monotykamary/dsh-client-ui-slots": "^0.1.0-rc.5",
36
+ "@monotykamary/dsh-client-web-react": "^0.1.0-rc.5",
37
+ "@monotykamary/dsh-client-modules": "^0.1.0-rc.5",
38
+ "@monotykamary/dsh-client-ui-theme": "^0.1.0-rc.5"
39
+ },
40
+ "devDependencies": {
41
+ "@types/react": "~18.3.1",
42
+ "@types/react-dom": "~18.3.0",
43
+ "typescript": "^6.0.3",
44
+ "@monotykamary/cordis": "^4.0.1",
45
+ "@monotykamary/dsh-client-runtime": "^0.1.0-rc.5",
46
+ "@monotykamary/dsh-invariants": "^0.1.0-rc.5",
47
+ "@monotykamary/dsh-client-test-runtime": "^0.1.0-rc.5",
48
+ "@monotykamary/cordis-plugin-loader": "^1.0.2"
49
+ },
50
+ "peerDependencies": {
51
+ "@monotykamary/cordis-plugin-loader": "^1.0.2",
52
+ "@monotykamary/dsh-invariants": "^0.1.0-rc.5",
53
+ "@monotykamary/cordis": "^4.0.1"
54
+ },
55
+ "files": [
56
+ "lib/index.js",
57
+ "lib/invariant.js",
58
+ "lib/types/**/*.d.ts"
59
+ ]
60
+ }