@deepseek-ai/dsh-client-web 0.1.7-alpha.2 → 0.1.7-rc.2

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,61 @@
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:
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
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: 2cedd1748973e13ad1b9a3b0ef89c0101d4ffb09
6
- README.zh.md: 882c338339b725f82aae4d931ab85a6d14650b42
5
+ /:
6
+ en: bd6e5fc50f07893f
7
+ zh: 0ee146674eb01770
8
+ /deepseek-ai-dsh-client-web:
9
+ en: 8423614617b7bbf1
10
+ zh: c99647136c800173
11
+ /deepseek-ai-dsh-client-web/summary:
12
+ en: 646bd0a246789400
13
+ zh: d875dc0d37f46338
14
+ /deepseek-ai-dsh-client-web/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /deepseek-ai-dsh-client-web/use-this-package:
18
+ en: 0c8b98946feedb0f
19
+ zh: ae10ae7b9ecbf89c
20
+ /deepseek-ai-dsh-client-web/use-this-package/what-boot-looks-like:
21
+ en: f3b01aa878764e5c
22
+ zh: f02eab65d441bfe0
23
+ /deepseek-ai-dsh-client-web/use-this-package/the-boot-page:
24
+ en: add354591107b80d
25
+ zh: 41136254a00d785e
26
+ /deepseek-ai-dsh-client-web/use-this-package/the-shared-module-table:
27
+ en: 8380cd70b861a3a8
28
+ zh: 5a331deb7ad8fca1
29
+ /deepseek-ai-dsh-client-web/use-this-package/configuration:
30
+ en: 942959cb40ee31a1
31
+ zh: 9c86ac2cf6a13976
32
+ /deepseek-ai-dsh-client-web/understand-the-implementation:
33
+ en: 9556ca6438410146
34
+ zh: 8a60572cb4caa04a
35
+ /deepseek-ai-dsh-client-web/understand-the-implementation/design-concept:
36
+ en: e18ec3ae51dcdc3f
37
+ zh: e454da2038979aec
38
+ /deepseek-ai-dsh-client-web/understand-the-implementation/two-stage-boot:
39
+ en: fb3cab79c1505627
40
+ zh: 875897afe593aeb3
41
+ /deepseek-ai-dsh-client-web/understand-the-implementation/boot-page-mechanics:
42
+ en: 83f9195138da9a82
43
+ zh: 135d7da735fb870a
44
+ /deepseek-ai-dsh-client-web/understand-the-implementation/source-map:
45
+ en: 1fe37d3730368e32
46
+ zh: 74c2d6980ce82e21
47
+ /deepseek-ai-dsh-client-web/further-exploration:
48
+ en: f2da70a33bba9086
49
+ zh: a775f6fe309150ea
50
+ /deepseek-ai-dsh-client-web/model-experience:
51
+ en: ec17334dcce181f3
52
+ zh: bc8bc6e25ecbf9e8
53
+ /deepseek-ai-dsh-client-web/model-experience/kv-cache-effect:
54
+ en: ca75c51c89c2c9b0
55
+ zh: de54f3467ef3b9be
56
+ /deepseek-ai-dsh-client-web/known-limitations-and-deferred-work:
57
+ en: f498ba05d4ea96d5
58
+ zh: c3e57dd8ca9b5bda
59
+ /deepseek-ai-dsh-client-web/known-limitations-and-deferred-work/dev-note:
60
+ en: 80ab3d00f7aac4de
61
+ zh: 3c08f30322413856
package/README.md CHANGED
@@ -52,6 +52,8 @@ The package accepts no plugin config of its own; the generated [configuration ca
52
52
  <a id="understand-the-implementation"></a>
53
53
  ## Understand the implementation
54
54
 
55
+ Menus use the shared `MenuSurface` material, including the macOS backing for background blur; custom content follows the [menu rules](../../../docs/web-styling.md#component-rules).
56
+
55
57
  <details>
56
58
  <summary>Implementation internals — click to expand</summary>
57
59
 
@@ -76,12 +78,14 @@ The boot kernel delegates manifest entry creation to Client Modules so live grap
76
78
  | File | Role |
77
79
  |---|---|
78
80
  | [`src/index.ts`](src/index.ts) | Library entry: `AppWebEntry`, `getStaticModules`, platform tables |
79
- | [`src/boot.ts`](src/boot.ts) | `AppWebEntry`: module stage, boot page, immediate-tier prefetch, then `bootClient` + `mountClient` |
81
+ | [`src/boot.ts`](src/boot.ts) | `AppWebEntry`: module stage, boot page, immediate-tier prefetch, window drag-rect watcher install, then `bootClient` + `mountClient` |
80
82
  | [`src/boot-client.ts`](src/boot-client.ts) | `bootClient` / `assertEntriesActive`: Loader mount, one entry per manifest row, activation audit |
81
83
  | [`src/mount.ts`](src/mount.ts) | `mountClient`: renderer handoff through a `uiRenderer` dependency fiber |
82
84
  | [`src/boot-page.ts`](src/boot-page.ts) | Framework-free boot page: spinner, per-entry status, failure rendering |
83
85
  | [`src/platform.ts`](src/platform.ts) | `PLATFORM_MODULES` / `PRELOADED_CLIENT_EXTERNALS`: the implicit external baseline |
84
86
  | [`src/seed.ts`](src/seed.ts) | Static module table handed to the loader at boot |
87
+ | [`src/window-drag/regions.ts`](src/window-drag/regions.ts) | The darwin app-region composition model, and the interactive selector `base.css` subtracts |
88
+ | [`src/window-drag/recall.ts`](src/window-drag/recall.ts) | The shell's one window drag-rect watcher (electron/electron#32341): measure the marked rows per frame and pulse the recall mark while they move |
85
89
 
86
90
  </details>
87
91
 
package/README.zh.md CHANGED
@@ -52,6 +52,8 @@ kind: "package-library"
52
52
  <a id="understand-the-implementation"></a>
53
53
  ## 理解实现
54
54
 
55
+ 菜单采用共享 `MenuSurface` 材质,包括用于背景模糊的 macOS 底层;自定义内容遵循[菜单规则](../../../docs/web-styling.zh.md#component-rules)。
56
+
55
57
  <details>
56
58
  <summary>实现细节——点击展开</summary>
57
59
 
@@ -76,12 +78,14 @@ kind: "package-library"
76
78
  | 文件 | 职责 |
77
79
  |---|---|
78
80
  | [`src/index.ts`](src/index.ts) | 库入口:`AppWebEntry`、`getStaticModules`、平台表 |
79
- | [`src/boot.ts`](src/boot.ts) | `AppWebEntry`:模块阶段、启动页、immediately 层级预取,随后调用 `bootClient` + `mountClient` |
81
+ | [`src/boot.ts`](src/boot.ts) | `AppWebEntry`:模块阶段、启动页、immediately 层级预取、安装窗口拖拽矩形 watcher,随后调用 `bootClient` + `mountClient` |
80
82
  | [`src/boot-client.ts`](src/boot-client.ts) | `bootClient` / `assertEntriesActive`:挂载 Loader、每个 manifest 行一个 entry、激活审计 |
81
83
  | [`src/mount.ts`](src/mount.ts) | `mountClient`:经 `uiRenderer` 依赖 fiber 完成渲染器交接 |
82
84
  | [`src/boot-page.ts`](src/boot-page.ts) | 无框架启动页:spinner、逐 entry 状态、失败渲染 |
83
85
  | [`src/platform.ts`](src/platform.ts) | `PLATFORM_MODULES` / `PRELOADED_CLIENT_EXTERNALS`:隐式 external 基座 |
84
86
  | [`src/seed.ts`](src/seed.ts) | 启动时交给 loader 的静态模块表 |
87
+ | [`src/window-drag/regions.ts`](src/window-drag/regions.ts) | darwin app-region 组合模型,以及 `base.css` 减除的交互元素选择器 |
88
+ | [`src/window-drag/recall.ts`](src/window-drag/recall.ts) | 外壳唯一的窗口拖拽矩形 watcher(electron/electron#32341):逐帧测量被打标行,并在它们移动期间脉冲 recall 标记 |
85
89
 
86
90
  </details>
87
91
 
package/lib/base.css CHANGED
@@ -39,36 +39,46 @@ html[data-platform='darwin'] body {
39
39
  background: transparent;
40
40
  }
41
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). */
42
+ /* Menu backings paint below page content but above the native vibrancy. */
47
43
  html[data-platform='darwin'] body {
48
- --dsw-specific-menu: rgba(248, 249, 250, 0.94);
44
+ isolation: isolate;
49
45
  }
50
46
 
51
- html[data-platform='darwin'] body[data-ds-dark-theme] {
52
- --dsw-specific-menu: rgba(48, 49, 54, 0.94);
47
+ /* macOS desktop: the one drag declaration. A chrome row marks itself in markup
48
+ (`data-window-drag`) and its own box becomes its draggable geometry, so no fixed
49
+ band has to match any row's height; the ui-theme app-region gate's CHROME_ROWS
50
+ manifest is the list of rows that must carry the mark. Electron composes
51
+ app-regions from geometry, not hit testing, so a surface covering a row would
52
+ otherwise drag the window instead of receiving clicks; every covering overlay
53
+ portals to document.body beside #root, which also puts it after every drag row
54
+ in document order, and one rule subtracts them all. */
55
+ html[data-platform='darwin'] [data-window-drag] {
56
+ -webkit-app-region: drag;
57
+ }
58
+
59
+ /* One-frame app-region pulse that forces Electron to recollect the window's drag
60
+ rects (electron#32341): a change to a computed app-region value is what dirties
61
+ them. The shell's window drag watcher (window-drag/recall.ts) sets this
62
+ attribute on the body for each frame in which a marked row's box moved, and
63
+ clears it on the frame that finds the geometry settled. The box it subtracts
64
+ is the body's own: the row marks inside still declare drag and win over it in
65
+ document order, so the pulse changes nothing about the composed surface — it
66
+ only makes Electron collect it again. */
67
+ html[data-platform='darwin'] [data-window-drag-recall] {
68
+ -webkit-app-region: no-drag;
53
69
  }
54
70
 
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
71
  html[data-platform='darwin'] body > :not(#root) {
61
72
  -webkit-app-region: no-drag;
62
73
  }
63
74
 
64
75
  /* 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. */
76
+ subtract themselves from any drag row they overlap, wherever they render.
77
+ Electron composes app-regions from geometry in document order, so a drag rule
78
+ must sit on a chrome row that precedes the overlays covering it — never on a
79
+ content container, which would override every overlay mounted earlier in the
80
+ DOM. One selector on the native elements, ARIA roles, and tabindexed custom
81
+ widgets replaces per-component no-drag opt-outs. */
72
82
  html[data-platform='darwin'] :is(
73
83
  button, a, input, select, textarea, summary, [contenteditable='true'], [tabindex],
74
84
  [role='dialog'], [role='alertdialog'], [role='menu'], [role='listbox'], [role='tooltip'],
package/lib/index.js CHANGED
@@ -220,6 +220,252 @@ function getStaticModules() {
220
220
  };
221
221
  }
222
222
  //#endregion
223
+ //#region lib/types/window-drag/regions.js
224
+ /**
225
+ * The window drag-region contract for the macOS desktop shell: Electron hands
226
+ * the page's `-webkit-app-region` boxes to the native window, which hit-tests
227
+ * them by geometry in DOM order, ignoring stacking. The property does not inherit:
228
+ * Blink collects one box per element whose own computed value is not `none`,
229
+ * skipping subtrees that are not visible, and the window applies them in that
230
+ * order: `drag` adds geometry, `no-drag` removes it. The composition is therefore
231
+ * equivalent to "the last matching box decides" — a point is draggable when the
232
+ * last collected box containing it is `drag`.
233
+ *
234
+ * This module is the executable statement of that rule. Production CSS authors
235
+ * the boxes; tests and the browser coverage scenario both decide points through
236
+ * this module so a claim about coverage has one meaning.
237
+ */
238
+ /**
239
+ * The attribute a chrome row puts on the element that owns its window drag.
240
+ * ui-web `base.css` turns the mark into the one darwin drag rule, so the row's
241
+ * own box is the window's draggable geometry.
242
+ */
243
+ const DRAG_MARK = "data-window-drag";
244
+ /**
245
+ * The attribute the shell sets for one frame to make Electron recollect the
246
+ * window's drag rects (electron#32341). While it is set, ui-web `base.css`
247
+ * subtracts the marked box; the row marks inside it still declare drag and win in
248
+ * document order, so the mark's only effect is that app-region values changed.
249
+ */
250
+ const RECALL_MARK = "data-window-drag-recall";
251
+ [
252
+ "button",
253
+ "a",
254
+ "input",
255
+ "select",
256
+ "textarea",
257
+ "summary",
258
+ "[contenteditable='true']",
259
+ "[tabindex]",
260
+ "[role='dialog']",
261
+ "[role='alertdialog']",
262
+ "[role='menu']",
263
+ "[role='listbox']",
264
+ "[role='tooltip']",
265
+ "[role='button']",
266
+ "[role='link']",
267
+ "[role='tab']",
268
+ "[role='menuitem']",
269
+ "[role='menuitemcheckbox']",
270
+ "[role='menuitemradio']",
271
+ "[role='option']",
272
+ "[role='checkbox']",
273
+ "[role='radio']",
274
+ "[role='switch']",
275
+ "[role='slider']",
276
+ "[role='combobox']",
277
+ "[role='textbox']"
278
+ ].join(", ");
279
+ //#endregion
280
+ //#region lib/types/window-drag/recall.js
281
+ /**
282
+ * Window drag recollection for the macOS desktop shell. Electron rebuilds the
283
+ * window's `-webkit-app-region` rects only when a style pass changes a computed
284
+ * app-region value (electron#32341), and Blink skips hidden boxes when it collects
285
+ * them, so a chrome row that appears, moves, or disappears without such a change
286
+ * leaves the native window holding the previous geometry: a press inside the row
287
+ * drags from where it used to be, and a row that slid under the pointer keeps its
288
+ * controls unreachable or its blank runs undraggable. This module owns the one
289
+ * watcher that closes that gap, so no chrome row has to know the trap.
290
+ *
291
+ * A row's viewport box is the measurement: every row in this composition shows and
292
+ * hides by mounting, unmounting, or moving, so a changed box is the signal. The
293
+ * watcher starts on a DOM change that touches a marked row — inside it, on it, on
294
+ * an ancestor that holds one, on a container it lives in, or adding or removing one
295
+ * — on a marked row's box resizing, or on a transition or animation starting on an
296
+ * element that holds one, and then measures every marked row once per frame for as
297
+ * long as any box keeps changing. Each frame that changed sets the recall mark, and
298
+ * the frame that finds the same geometry clears it and stops once the short grace
299
+ * window below has passed: that clear is the collection the steady state comes from.
300
+ * @module @deepseek-ai/dsh-client-web/src/window-drag/recall
301
+ */
302
+ /** The selector of an element that owns a window drag region. */
303
+ const ROW_SELECTOR = `[${DRAG_MARK}]`;
304
+ /**
305
+ * Quiet frames a report tolerates before the loop stops again. A CSS transition's
306
+ * first frame still reports the box's from-value, so a report that arrives before
307
+ * the surface starts moving must not end the loop on its first unchanged sample.
308
+ */
309
+ const GRACE_FRAMES = 2;
310
+ /** The event types that mark a box starting or finishing a transition or animation. */
311
+ const MOTION_EVENTS = [
312
+ "transitionstart",
313
+ "transitionend",
314
+ "animationstart",
315
+ "animationend"
316
+ ];
317
+ /**
318
+ * Watch the window's marked drag rows and pulse the recall mark whenever their
319
+ * geometry can have moved. Installs nothing outside the darwin platform, where no
320
+ * app-region rule exists.
321
+ * @param options - the document to watch and the two scheduling seams.
322
+ * @returns a disposer that stops watching, drops any pending frame, and clears the mark.
323
+ */
324
+ function installWindowDragRecall(options) {
325
+ const doc = options.document;
326
+ if (doc.documentElement.dataset.platform !== "darwin") return () => {};
327
+ const scheduleFrame = options.scheduleFrame ?? defaultScheduleFrame;
328
+ const watchBox = options.watchBox ?? defaultWatchBox;
329
+ /** The last frame's measurement per row, the baseline a change is read against. */
330
+ let geometry = /* @__PURE__ */ new Map();
331
+ /** One box watcher per row currently rendered. */
332
+ const boxes = /* @__PURE__ */ new Map();
333
+ let scheduled = false;
334
+ let disposed = false;
335
+ let cancelPending;
336
+ /** Quiet frames left before the loop may stop; refreshed by every report. */
337
+ let grace = 0;
338
+ /**
339
+ * Measure every marked row and pulse when any box differs from the last frame's.
340
+ * Runs once per frame while the surface keeps moving.
341
+ */
342
+ const measure = () => {
343
+ doc.body.removeAttribute(RECALL_MARK);
344
+ const rows = Array.from(doc.querySelectorAll(ROW_SELECTOR));
345
+ watchBoxes(rows);
346
+ const next = new Map(rows.map((row) => [row, readBox(row)]));
347
+ const moved = rows.length !== geometry.size || rows.some((row) => geometry.get(row) !== next.get(row));
348
+ geometry = next;
349
+ if (moved) {
350
+ doc.body.setAttribute(RECALL_MARK, "");
351
+ grace = 0;
352
+ schedule();
353
+ return;
354
+ }
355
+ if (grace === 0) return;
356
+ grace -= 1;
357
+ schedule();
358
+ };
359
+ /** Report that the surface may be moving, opening the grace window again. */
360
+ const arm = () => {
361
+ grace = GRACE_FRAMES;
362
+ schedule();
363
+ };
364
+ /** Schedule one frame, keeping at most one pending. */
365
+ const schedule = () => {
366
+ if (scheduled) return;
367
+ scheduled = true;
368
+ cancelPending = scheduleFrame(() => {
369
+ scheduled = false;
370
+ cancelPending = void 0;
371
+ if (disposed) return;
372
+ measure();
373
+ });
374
+ };
375
+ /** Keep one box watcher per live row, re-watching when its frame moves or unmounts. */
376
+ const watchBoxes = (rows) => {
377
+ for (const [row, dispose] of boxes) {
378
+ if (rows.includes(row)) continue;
379
+ dispose();
380
+ boxes.delete(row);
381
+ }
382
+ for (const row of rows) {
383
+ if (boxes.has(row)) continue;
384
+ boxes.set(row, watchBox(row, arm));
385
+ }
386
+ };
387
+ /**
388
+ * Whether a motion event can be moving a marked row: the transitioning element is
389
+ * one, holds one, or lives inside one.
390
+ * @param target - the event target to classify.
391
+ * @returns true when the drag surface has to be re-measured.
392
+ */
393
+ const movesRows = (target) => {
394
+ if (!(target instanceof Element)) return false;
395
+ return target.closest(ROW_SELECTOR) !== null || target.querySelector(ROW_SELECTOR) !== null;
396
+ };
397
+ /** Re-arm for a transition or animation that can be sliding a marked row. */
398
+ const onMotion = (event) => {
399
+ if (movesRows(event.target)) arm();
400
+ };
401
+ const observer = new MutationObserver((records) => {
402
+ if (records.some((record) => touchesRows(record, geometry.keys()))) arm();
403
+ });
404
+ observer.observe(doc.body, {
405
+ subtree: true,
406
+ childList: true,
407
+ attributes: true,
408
+ characterData: true
409
+ });
410
+ for (const type of MOTION_EVENTS) doc.addEventListener(type, onMotion, true);
411
+ arm();
412
+ return () => {
413
+ disposed = true;
414
+ observer.disconnect();
415
+ for (const type of MOTION_EVENTS) doc.removeEventListener(type, onMotion, true);
416
+ if (cancelPending !== void 0) cancelPending();
417
+ for (const dispose of boxes.values()) dispose();
418
+ boxes.clear();
419
+ geometry.clear();
420
+ doc.body.removeAttribute(RECALL_MARK);
421
+ };
422
+ }
423
+ /** The box a row owns this frame, in viewport pixels. */
424
+ function readBox(row) {
425
+ const rect = row.getBoundingClientRect();
426
+ return `${rect.x},${rect.y},${rect.width},${rect.height}`;
427
+ }
428
+ /**
429
+ * Whether one DOM change can have moved the drag surface: a child list change in a
430
+ * container that holds a marked row, or an attribute or text change that lands on a
431
+ * marked row, inside one, or on an element holding one (a panel's open flag, a
432
+ * column's width). A child list change in a container without a marked row is left
433
+ * alone — a row whose box grows because its content did is a resize, which the box
434
+ * watcher reports — and so is a mounted overlay, whose own app-region value is a
435
+ * change of its own.
436
+ * @param record - the mutation record to classify.
437
+ * @param rows - the rows the last frame measured.
438
+ * @returns true when the drag surface has to be re-measured.
439
+ */
440
+ function touchesRows(record, rows) {
441
+ if (record.attributeName === "data-window-drag-recall") return false;
442
+ for (const node of [...record.addedNodes, ...record.removedNodes]) if (node instanceof Element && (node.matches(ROW_SELECTOR) || node.querySelector(ROW_SELECTOR) !== null)) return true;
443
+ const target = record.target instanceof Element ? record.target : record.target.parentElement;
444
+ /* v8 ignore next -- the observer watches body's subtree, so a text target always has a parent element. */
445
+ if (target === null) return false;
446
+ if (record.type === "childList") {
447
+ for (const row of rows) if (target.contains(row)) return true;
448
+ return false;
449
+ }
450
+ return target.closest(ROW_SELECTOR) !== null || target.querySelector(ROW_SELECTOR) !== null;
451
+ }
452
+ /** Schedule one frame through the browser's animation frame queue. */
453
+ function defaultScheduleFrame(frame) {
454
+ const handle = requestAnimationFrame(frame);
455
+ return () => {
456
+ cancelAnimationFrame(handle);
457
+ };
458
+ }
459
+ /** Watch one row's border box where the document has a `ResizeObserver`. */
460
+ function defaultWatchBox(row, changed) {
461
+ if (typeof ResizeObserver !== "function") return () => {};
462
+ const observer = new ResizeObserver(changed);
463
+ observer.observe(row);
464
+ return () => {
465
+ observer.disconnect();
466
+ };
467
+ }
468
+ //#endregion
223
469
  //#region lib/types/boot.js
224
470
  /**
225
471
  * Web boot kernel. It owns only the module system, Cordis loader, and a
@@ -234,6 +480,7 @@ var AppWebEntry = class {
234
480
  seams;
235
481
  page;
236
482
  ctx;
483
+ stopDragRecall;
237
484
  modules;
238
485
  manifest;
239
486
  /**
@@ -279,6 +526,7 @@ var AppWebEntry = class {
279
526
  if (onFailure === void 0 || state !== "failed") this.page.setState(name, state);
280
527
  }
281
528
  });
529
+ this.stopDragRecall = installWindowDragRecall({ document: this.container.ownerDocument });
282
530
  await mountClient(ctx, this.container);
283
531
  } catch (reason) {
284
532
  console.error(reason);
@@ -288,6 +536,8 @@ var AppWebEntry = class {
288
536
  }
289
537
  /** Dispose the client plugin tree and whichever page owns the mount point. */
290
538
  async dispose() {
539
+ this.stopDragRecall?.();
540
+ this.stopDragRecall = void 0;
291
541
  const ctx = this.ctx;
292
542
  this.ctx = void 0;
293
543
  if (ctx !== void 0) await ctx.fiber.dispose();
@@ -8,6 +8,7 @@ export declare class AppWebEntry {
8
8
  private readonly seams;
9
9
  private readonly page;
10
10
  private ctx;
11
+ private stopDragRecall;
11
12
  private modules;
12
13
  private manifest;
13
14
  /**
@@ -0,0 +1,25 @@
1
+ /** How the shell installs the one window drag watcher. */
2
+ export interface WindowDragRecallOptions {
3
+ /** Document whose marked rows are watched. */
4
+ readonly document: Document;
5
+ /**
6
+ * Schedules one frame callback and returns its canceller. The callback runs
7
+ * asynchronously, as `requestAnimationFrame` does.
8
+ */
9
+ readonly scheduleFrame?: (frame: () => void) => () => void;
10
+ /**
11
+ * Watches one row's border box and calls `changed` whenever it resizes, returning
12
+ * a disposer. Defaults to a `ResizeObserver`; a document without one watches no
13
+ * boxes and relies on the DOM changes alone.
14
+ */
15
+ readonly watchBox?: (row: Element, changed: () => void) => () => void;
16
+ }
17
+ /**
18
+ * Watch the window's marked drag rows and pulse the recall mark whenever their
19
+ * geometry can have moved. Installs nothing outside the darwin platform, where no
20
+ * app-region rule exists.
21
+ * @param options - the document to watch and the two scheduling seams.
22
+ * @returns a disposer that stops watching, drops any pending frame, and clears the mark.
23
+ */
24
+ export declare function installWindowDragRecall(options: WindowDragRecallOptions): () => void;
25
+ //# sourceMappingURL=recall.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The window drag-region contract for the macOS desktop shell: Electron hands
3
+ * the page's `-webkit-app-region` boxes to the native window, which hit-tests
4
+ * them by geometry in DOM order, ignoring stacking. The property does not inherit:
5
+ * Blink collects one box per element whose own computed value is not `none`,
6
+ * skipping subtrees that are not visible, and the window applies them in that
7
+ * order: `drag` adds geometry, `no-drag` removes it. The composition is therefore
8
+ * equivalent to "the last matching box decides" — a point is draggable when the
9
+ * last collected box containing it is `drag`.
10
+ *
11
+ * This module is the executable statement of that rule. Production CSS authors
12
+ * the boxes; tests and the browser coverage scenario both decide points through
13
+ * this module so a claim about coverage has one meaning.
14
+ */
15
+ /**
16
+ * One collected app-region box, in DOM order.
17
+ *
18
+ * Coordinates are viewport CSS pixels, the space `getBoundingClientRect`
19
+ * reports, so collection and composition never rescale.
20
+ */
21
+ export interface RegionRect {
22
+ /** Left edge, viewport CSS pixels. */
23
+ readonly x: number;
24
+ /** Top edge, viewport CSS pixels. */
25
+ readonly y: number;
26
+ /** Box width in CSS pixels. */
27
+ readonly width: number;
28
+ /** Box height in CSS pixels. */
29
+ readonly height: number;
30
+ /** Whether this box declares `drag` (true) or `no-drag` (false). */
31
+ readonly draggable: boolean;
32
+ }
33
+ /**
34
+ * The attribute a chrome row puts on the element that owns its window drag.
35
+ * ui-web `base.css` turns the mark into the one darwin drag rule, so the row's
36
+ * own box is the window's draggable geometry.
37
+ */
38
+ export declare const DRAG_MARK = "data-window-drag";
39
+ /**
40
+ * The attribute the shell sets for one frame to make Electron recollect the
41
+ * window's drag rects (electron#32341). While it is set, ui-web `base.css`
42
+ * subtracts the marked box; the row marks inside it still declare drag and win in
43
+ * document order, so the mark's only effect is that app-region values changed.
44
+ */
45
+ export declare const RECALL_MARK = "data-window-drag-recall";
46
+ /**
47
+ * The interactive-element selector that subtracts a control from any drag row it
48
+ * overlaps. ui-web `base.css` declares it for the darwin platform; this constant
49
+ * is the single source both that sheet and its contract spec compare against, so
50
+ * the two cannot drift.
51
+ */
52
+ export declare const INTERACTIVE_SELECTOR: string;
53
+ /**
54
+ * Whether a box contains a viewport point. Edges belong to the box, matching
55
+ * the half-open rectangles the window hit test uses.
56
+ * @param rect - the collected box.
57
+ * @param x - viewport x in CSS pixels.
58
+ * @param y - viewport y in CSS pixels.
59
+ * @returns true when the point lies inside the box.
60
+ */
61
+ export declare function containsPoint(rect: RegionRect, x: number, y: number): boolean;
62
+ /**
63
+ * Decide whether the window would drag when pressed at a point.
64
+ * @param regions - collected boxes in DOM order.
65
+ * @param x - viewport x in CSS pixels.
66
+ * @param y - viewport y in CSS pixels.
67
+ * @returns true when the last box containing the point is a drag box; false
68
+ * when it is a no-drag box or no box contains the point.
69
+ */
70
+ export declare function isDraggableAt(regions: readonly RegionRect[], x: number, y: number): boolean;
71
+ //# sourceMappingURL=regions.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.7-alpha.2",
4
+ "version": "0.1.7-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,15 +32,15 @@
32
32
  "react": "^18.2.0",
33
33
  "react-dom": "^18.2.0",
34
34
  "typescript": "^6.0.3",
35
- "@deepseek-ai/dsh-client-modules": "0.1.7-alpha.2",
36
35
  "@deepseek-ai/cordis-plugin-loader": "~1.0.5",
37
- "@deepseek-ai/dsh-client-store": "0.1.7-alpha.2",
38
- "@deepseek-ai/dsh-client-ui-dockkit": "0.1.7-alpha.2",
39
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-alpha.2",
40
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-alpha.2",
41
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-alpha.2",
36
+ "@deepseek-ai/dsh-client-modules": "0.1.7-rc.2",
37
+ "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
38
+ "@deepseek-ai/dsh-client-ui-dockkit": "0.1.7-rc.2",
39
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
40
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
41
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
42
42
  "@deepseek-ai/cordis": "~4.0.4",
43
- "@deepseek-ai/dsh-host-webserver": "0.1.7-alpha.2"
43
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@deepseek-ai/cordis": "~4.0.4"