@deepseek-ai/dsh-client-web 0.1.6-alpha.1 → 0.1.7-alpha.1

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
@@ -2,5 +2,5 @@
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
4
  # pnpm run verify-translation-pairing --write packages/client/web/README.md
5
- README.md: 7bdcec9e2a964b746cbd808497f9222e2b4150fa
6
- README.zh.md: a7243e52a7bababebb75a0ee9f4e22a99fa83337
5
+ README.md: 2cedd1748973e13ad1b9a3b0ef89c0101d4ffb09
6
+ README.zh.md: 882c338339b725f82aae4d931ab85a6d14650b42
package/README.md CHANGED
@@ -27,6 +27,8 @@ English | [中文](README.zh.md)
27
27
 
28
28
  Use it when you assemble the browser application: `apps/web`'s Vite entry runs `new AppWebEntry(container).run()` against the mount point, and the boot page carries the user through activation. Ordinary browser callers pass no options. A pre-injected page transport is the default ahead of the `seams` override: when `globalThis.__DSH_TRANSPORT__` carries `loadBundle`, the module stage adopts it as the bundle transport and skips the immediate-tier HTTP prefetch, while explicit `seams` still win (for example jsdom tests, where external `<script>` execution cannot reach the page context).
29
29
 
30
+ Static application pages install `__DSH_BOOT_READY__` before the entry runs. The boot page renders immediately while `run()` waits; the page owner applies the Host rows with `applyIndexInjections` (also exported from `./injections`) and resolves the deferred after all scripts finish. A rejected deferred renders a boot failure unless the caller supplies `run(onFailure)` to present the error externally while retaining the loading page. Desktop uses this callback to request native recovery. Desktop and WebWorker share the injection interpreter; server-side `tapIndex` HTML transforms apply only to served documents.
31
+
30
32
  The shell base styles apply automatic CJK/Latin spacing to ordinary content in supporting browsers. Semantic code and terminal, diff, read, and search output containers retain literal source spacing and column alignment; browsers without `text-autospace` support ignore both declarations.
31
33
 
32
34
  ### What boot looks like
@@ -67,6 +69,8 @@ The kernel owns exactly three things: the module system, the Cordis Loader, and
67
69
 
68
70
  The boot page is plain DOM with local CSS whose fallback fonts and colors match the theme tokens that arrive during loading. `internal/status` events drive one spinner node and per-entry labels; hydration preserves the node and animation phase through the application commit, and `fail()` renders the thrown reason. React mounting, slot rendering, and assembly live in `ui-renderer`; `ui-layout` owns the assembled browser-title projection.
69
71
 
72
+ The boot kernel delegates manifest entry creation to Client Modules so live graph synchronization owns the same entry identities after startup. The initial activation audit remains strict; later page-local failures appear in Settings → Plugins → Plugin list.
73
+
70
74
  ### Source map
71
75
 
72
76
  | File | Role |
package/README.zh.md CHANGED
@@ -27,6 +27,8 @@ kind: "package-library"
27
27
 
28
28
  组装浏览器应用时使用它:`apps/web` 的 Vite 入口对挂载点运行 `new AppWebEntry(container).run()`,启动页会在激活过程中向用户展示进度。普通浏览器调用方不传任何选项。默认使用预注入的页面传输,除非提供 `seams` 覆盖:当 `globalThis.__DSH_TRANSPORT__` 携带 `loadBundle` 时,模块阶段将其采纳为 bundle 传输并跳过 `immediately` 层级的 HTTP 预取,而显式 `seams` 仍然优先(例如外部 `<script>` 执行无法到达页面上下文的 jsdom 测试)。
29
29
 
30
+ 静态应用页面在入口运行前安装 `__DSH_BOOT_READY__`。`run()` 等待期间会立即显示启动页;页面所有者通过 `applyIndexInjections`(也从 `./injections` 导出)应用 Host 注入项,并在所有脚本完成后兑现延迟对象。延迟对象拒绝时显示启动失败;若调用方提供 `run(onFailure)`,则由外部呈现错误并保留加载页。Desktop 使用该回调请求原生恢复。Desktop 与 WebWorker 共享注入解释器;服务端 `tapIndex` HTML 转换仅适用于服务端提供的文档。
31
+
30
32
  外壳基础样式会在支持的浏览器中为普通内容自动添加中西文间距。语义化代码以及终端、diff、读取和搜索输出容器会保留源码中的原始间距和列对齐;不支持 `text-autospace` 的浏览器会忽略这两项声明。
31
33
 
32
34
  ### 启动过程是怎样的
@@ -67,6 +69,8 @@ kind: "package-library"
67
69
 
68
70
  启动页是原生 DOM 加本地 CSS,其回退字体与颜色匹配加载期间到达的主题 token。`internal/status` 事件驱动一个 spinner 节点与逐 entry 标签;hydrate 会保留该节点与动画相位直到应用提交,`fail()` 渲染抛出的原因。React 挂载、slot 渲染与应用组装位于 `ui-renderer`;`ui-layout` 拥有组装后的浏览器标题投影。
69
71
 
72
+ 启动内核把清单条目创建交给 Client Modules,使启动后的动态图同步继续持有相同的条目身份。初始激活审计仍然严格;后续页面本地失败显示在「设置 → 插件 → 插件列表」。
73
+
70
74
  ### 源码地图
71
75
 
72
76
  | 文件 | 职责 |
@@ -0,0 +1,40 @@
1
+ //#region lib/types/apply-injections.js
2
+ function assertNever(row) {
3
+ throw new Error(`web boot: unknown index injection row ${JSON.stringify(row)}`);
4
+ }
5
+ /**
6
+ * Execute every row in table order.
7
+ * @param rows - Injection table from the boot payload.
8
+ * @param loadScript - Executes one script-src row through the page owner's asset transport.
9
+ */
10
+ async function applyIndexInjections(rows, loadScript) {
11
+ for (const row of rows) switch (row.kind) {
12
+ case "global":
13
+ globalThis[row.name] = row.value;
14
+ break;
15
+ case "script": {
16
+ const el = document.createElement("script");
17
+ el.textContent = row.text;
18
+ (row.placement === "head" ? document.head : document.body).append(el);
19
+ break;
20
+ }
21
+ case "script-src":
22
+ await loadScript(row.src);
23
+ break;
24
+ case "script-preload": break;
25
+ case "style": {
26
+ const el = document.createElement("style");
27
+ el.textContent = row.text;
28
+ document.head.append(el);
29
+ break;
30
+ }
31
+ case "html":
32
+ (row.placement === "head" ? document.head : document.body).insertAdjacentHTML("beforeend", row.html);
33
+ break;
34
+ default: assertNever(row);
35
+ }
36
+ }
37
+ //#endregion
38
+ export { applyIndexInjections };
39
+
40
+ //# sourceMappingURL=apply-injections.js.map
package/lib/base.css CHANGED
@@ -32,6 +32,53 @@ body {
32
32
  text-autospace: normal;
33
33
  }
34
34
 
35
+ /* macOS desktop (the Electron preload sets data-platform): the window's
36
+ sidebar vibrancy shows only through a transparent page background. */
37
+ html[data-platform='darwin'],
38
+ html[data-platform='darwin'] body {
39
+ background: transparent;
40
+ }
41
+
42
+ /* The transparent window disables backdrop-filter: Chromium composites it
43
+ against the root surface, which vibrancy leaves transparent, so the blur in
44
+ --dsw-menu-backdrop-filter renders as nothing and half-opaque menu fills
45
+ let underlying text bleed through. Compensate with near-opaque fills on the
46
+ same design-platform hues (light 248 249 250 / dark 48 49 54). */
47
+ html[data-platform='darwin'] body {
48
+ --dsw-specific-menu: rgba(248, 249, 250, 0.94);
49
+ }
50
+
51
+ html[data-platform='darwin'] body[data-ds-dark-theme] {
52
+ --dsw-specific-menu: rgba(48, 49, 54, 0.94);
53
+ }
54
+
55
+ /* macOS desktop: the app frame publishes window drag bands, and Electron
56
+ composes app-regions from geometry, not hit testing — a surface covering a
57
+ band would otherwise drag the window instead of receiving clicks. Every
58
+ overlay portals to document.body beside #root, so one rule subtracts them
59
+ all; drag surfaces all live inside #root. */
60
+ html[data-platform='darwin'] body > :not(#root) {
61
+ -webkit-app-region: no-drag;
62
+ }
63
+
64
+ /* Inside #root, interactive controls and raised surfaces (dialogs, menus)
65
+ subtract themselves from any drag band they overlap, wherever they render.
66
+ Electron composes app-regions from geometry in document order, so this works
67
+ only because the bands are declared before all content (AppFrame's first
68
+ child; the sidebar rows that precede the column's overlay slots) — a drag
69
+ rule on a content container would override every overlay mounted earlier in
70
+ the DOM. One selector on the native elements, ARIA roles, and tabindexed
71
+ custom widgets replaces per-component no-drag opt-outs. */
72
+ html[data-platform='darwin'] :is(
73
+ button, a, input, select, textarea, summary, [contenteditable='true'], [tabindex],
74
+ [role='dialog'], [role='alertdialog'], [role='menu'], [role='listbox'], [role='tooltip'],
75
+ [role='button'], [role='link'], [role='tab'], [role='menuitem'], [role='menuitemcheckbox'],
76
+ [role='menuitemradio'], [role='option'], [role='checkbox'], [role='radio'], [role='switch'],
77
+ [role='slider'], [role='combobox'], [role='textbox']
78
+ ) {
79
+ -webkit-app-region: no-drag;
80
+ }
81
+
35
82
  code,
36
83
  pre,
37
84
  [data-diff],
@@ -1,7 +1,6 @@
1
1
  /* The framework-free boot page cannot depend on theme delivery succeeding. */
2
2
 
3
3
  .boot {
4
- --dsh-boot-bg: #fff;
5
4
  --dsh-boot-label-primary: #0f1115;
6
5
  --dsh-boot-label-secondary: #61666b;
7
6
  --dsh-boot-label-tertiary: #81858c;
@@ -11,11 +10,10 @@
11
10
  height: 100%;
12
11
  display: grid;
13
12
  place-items: center;
14
- background: var(--dsw-alias-bg-base, var(--dsh-boot-bg));
13
+ background: var(--dsw-alias-bg-base, var(--dsh-boot-bg, Canvas));
15
14
  }
16
15
 
17
16
  :global(body[data-ds-dark-theme]) .boot {
18
- --dsh-boot-bg: #151517;
19
17
  --dsh-boot-label-primary: #f9fafb;
20
18
  --dsh-boot-label-secondary: #cfd3d6;
21
19
  --dsh-boot-label-tertiary: #adb2b8;
package/lib/index.js CHANGED
@@ -56,11 +56,9 @@ async function bootClient(options) {
56
56
  onEntryState?.(entry.options.name, STATE_LABELS[entry.fiber.state]);
57
57
  });
58
58
  const rows = manifest.plugins.map((row) => row.id);
59
- await Promise.all(rows.map(async (name) => {
60
- onEntryState?.(name, "loading");
61
- const id = await loader.create({ name });
62
- if (loader.resolve(id).fiber === void 0) onEntryState?.(name, "failed");
63
- }));
59
+ for (const name of rows) onEntryState?.(name, "loading");
60
+ await options.modules.entries.start(loader, manifest);
61
+ for (const entry of loader.entries()) if (entry.fiber === void 0) onEntryState?.(entry.options.name, "failed");
64
62
  await loader.await();
65
63
  assertEntriesActive(ctx);
66
64
  }
@@ -247,9 +245,10 @@ var AppWebEntry = class {
247
245
  /**
248
246
  * Load and activate every client entry, then hand the mount point to the
249
247
  * UI renderer. Plugin failures remain visible on the boot page.
250
- * @returns Resolves after application mount or failure rendering.
248
+ * @param onFailure - Optional carrier-owned fatal presentation; keeps the boot page visible.
249
+ * @returns Resolves after application mount or failure reporting.
251
250
  */
252
- async run() {
251
+ async run(onFailure) {
253
252
  try {
254
253
  await globalThis.__DSH_BOOT_READY__?.promise;
255
254
  const win = globalThis;
@@ -273,13 +272,14 @@ var AppWebEntry = class {
273
272
  modules: this.modules,
274
273
  manifest: this.manifest,
275
274
  onEntryState: (name, state) => {
276
- this.page.setState(name, state);
275
+ if (onFailure === void 0 || state !== "failed") this.page.setState(name, state);
277
276
  }
278
277
  });
279
278
  await mountClient(ctx, this.container);
280
279
  } catch (reason) {
281
280
  console.error(reason);
282
- this.page.fail(reason instanceof Error ? reason.message : String(reason));
281
+ if (onFailure !== void 0) onFailure(reason);
282
+ else this.page.fail(reason instanceof Error ? reason.message : String(reason));
283
283
  }
284
284
  }
285
285
  /** Dispose the client plugin tree and whichever page owns the mount point. */
@@ -316,6 +316,43 @@ const PLATFORM_MODULES = [
316
316
  /** Client-bundle specifiers whose factories the parser preloads before the shell starts. */
317
317
  const PRELOADED_CLIENT_EXTERNALS = [];
318
318
  //#endregion
319
- export { AppWebEntry, PLATFORM_MODULES, PRELOADED_CLIENT_EXTERNALS, getStaticModules };
319
+ //#region lib/types/apply-injections.js
320
+ function assertNever(row) {
321
+ throw new Error(`web boot: unknown index injection row ${JSON.stringify(row)}`);
322
+ }
323
+ /**
324
+ * Execute every row in table order.
325
+ * @param rows - Injection table from the boot payload.
326
+ * @param loadScript - Executes one script-src row through the page owner's asset transport.
327
+ */
328
+ async function applyIndexInjections(rows, loadScript) {
329
+ for (const row of rows) switch (row.kind) {
330
+ case "global":
331
+ globalThis[row.name] = row.value;
332
+ break;
333
+ case "script": {
334
+ const el = document.createElement("script");
335
+ el.textContent = row.text;
336
+ (row.placement === "head" ? document.head : document.body).append(el);
337
+ break;
338
+ }
339
+ case "script-src":
340
+ await loadScript(row.src);
341
+ break;
342
+ case "script-preload": break;
343
+ case "style": {
344
+ const el = document.createElement("style");
345
+ el.textContent = row.text;
346
+ document.head.append(el);
347
+ break;
348
+ }
349
+ case "html":
350
+ (row.placement === "head" ? document.head : document.body).insertAdjacentHTML("beforeend", row.html);
351
+ break;
352
+ default: assertNever(row);
353
+ }
354
+ }
355
+ //#endregion
356
+ export { AppWebEntry, PLATFORM_MODULES, PRELOADED_CLIENT_EXTERNALS, applyIndexInjections, getStaticModules };
320
357
 
321
358
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Page-side interpreter for the structured index injection table. The served
3
+ * form renders the same rows into index.html text; a static worker page has
4
+ * no served HTML, so it executes the table directly. Rows execute strictly in
5
+ * table order, so a global row lands before the scripts that read it.
6
+ */
7
+ import type { IndexInjection } from '@deepseek-ai/dsh-host-webserver';
8
+ /**
9
+ * Execute every row in table order.
10
+ * @param rows - Injection table from the boot payload.
11
+ * @param loadScript - Executes one script-src row through the page owner's asset transport.
12
+ */
13
+ export declare function applyIndexInjections(rows: readonly IndexInjection[], loadScript: (src: string) => Promise<void>): Promise<void>;
14
+ //# sourceMappingURL=apply-injections.d.ts.map
@@ -19,9 +19,10 @@ export declare class AppWebEntry {
19
19
  /**
20
20
  * Load and activate every client entry, then hand the mount point to the
21
21
  * UI renderer. Plugin failures remain visible on the boot page.
22
- * @returns Resolves after application mount or failure rendering.
22
+ * @param onFailure - Optional carrier-owned fatal presentation; keeps the boot page visible.
23
+ * @returns Resolves after application mount or failure reporting.
23
24
  */
24
- run(): Promise<void>;
25
+ run(onFailure?: (reason: unknown) => void): Promise<void>;
25
26
  /** Dispose the client plugin tree and whichever page owns the mount point. */
26
27
  dispose(): Promise<void>;
27
28
  /** Prefetch stage-one bundles and their dynamic requests before concurrent plugin imports. */
@@ -8,4 +8,5 @@
8
8
  export { AppWebEntry, type BootSeams } from './boot.ts';
9
9
  export { getStaticModules } from './seed.ts';
10
10
  export { PLATFORM_MODULES, PRELOADED_CLIENT_EXTERNALS, type PlatformModule } from './platform.ts';
11
+ export { applyIndexInjections } from './apply-injections.ts';
11
12
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-web",
3
3
  "description": "Web boot kernel: static module table, Cordis loader, framework-free boot page, and UI-renderer handoff",
4
- "version": "0.1.6-alpha.1",
4
+ "version": "0.1.7-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -19,7 +19,11 @@
19
19
  "default": "./lib/index.js"
20
20
  },
21
21
  "./src/*": "./src/*",
22
- "./package.json": "./package.json"
22
+ "./package.json": "./package.json",
23
+ "./injections": {
24
+ "types": "./lib/types/apply-injections.d.ts",
25
+ "default": "./lib/apply-injections.js"
26
+ }
23
27
  },
24
28
  "license": "MIT",
25
29
  "devDependencies": {
@@ -28,21 +32,23 @@
28
32
  "react": "^18.2.0",
29
33
  "react-dom": "^18.2.0",
30
34
  "typescript": "^6.0.3",
31
- "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
32
- "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
33
- "@deepseek-ai/dsh-client-ui-dockkit": "^0.1.6-alpha.1",
34
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1",
35
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
36
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
37
- "@deepseek-ai/dsh-client-modules": "^0.1.6-alpha.1",
38
- "@deepseek-ai/cordis": "^4.0.2"
35
+ "@deepseek-ai/dsh-client-modules": "^0.1.7-alpha.1",
36
+ "@deepseek-ai/dsh-client-ui-dockkit": "^0.1.7-alpha.1",
37
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-alpha.1",
38
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-alpha.1",
39
+ "@deepseek-ai/cordis": "^4.0.3",
40
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.4",
41
+ "@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1",
42
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.1",
43
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-alpha.1"
39
44
  },
40
45
  "peerDependencies": {
41
- "@deepseek-ai/cordis": "^4.0.2"
46
+ "@deepseek-ai/cordis": "^4.0.3"
42
47
  },
43
48
  "files": [
44
49
  "lib/index.js",
45
50
  "lib/**/*.css",
51
+ "lib/apply-injections.js",
46
52
  "lib/types/**/*.d.ts"
47
53
  ]
48
54
  }