dsh-background-by-model 0.4.4 → 0.4.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.md CHANGED
@@ -225,6 +225,13 @@ No. Both were removed in 0.3.0; each rule uses a static image.
225
225
 
226
226
  ## Recent Optimizations
227
227
 
228
+ ### v0.4.5
229
+
230
+ - **`optionalWhen` finally means what its name says** — The field is documented as "the check runs while this surface is on screen", but the probe was evaluating the exact opposite: an anchor that was *not* in the DOM produced *n/a*, and one that *was* produced the failure. Two of the three dock contracts and both conversation-view contracts were therefore testing the wrong state — which is how a healthy dock came back as `停靠空位 失效` with "no element matches `[data-dockkit-empty]`". The helper now reads the anchor as a positive predicate, and every anchor was renamed to say what it means (`dockHasTab`, `dockHasNoTab`, `chatHasMessages`, `paneFloated`, …) so an anchor named for an absence can no longer invert a check silently. Four tests pin the resulting state table in both directions.
231
+ - **A contract about the message column stops firing on a session that has no messages** — `[data-chat-flow]` is rendered only when the session *has* messages, so its absence is the normal state of a brand-new session (the host's `data-content-phase="hero"` screen) and of the trajectory tab. The anchor was "the conversation scrollport exists", which is true in all three states, so the check reported a rename on the hero screen and on **轨迹** — the second failure the panel showed. It is now keyed on `[data-chat-turn]`, the host's own mark on every message row, which is the one thing that is present exactly when a column should exist. Measured: hero → 0 flow / 0 turn rows; after one message → 1 flow / 4 turn rows; trajectory tab → 0 flow / 0 turn rows.
232
+ - **`visible` — presence is not the same question as "on screen"** — A selector check marked `visible: true` now also requires the element to be on screen, so an element the host parks with `visibility:hidden` cannot satisfy a contract about a surface the user is looking at. The dock's empty seat is deliberately *not* marked: the host keeps it rendered and hidden as tabs open and close, so asking whether it is visible would be asking about a state the host uses both ways.
233
+ - **One more anchor rule, written down** — An anchor must name the state in which its attribute is **expected**, never a neighbour of it that happens to appear at the same time. All four false-red bugs in this feature have been the same mistake: a mounted surface that correctly renders nothing for the attribute to sit on.
234
+
228
235
  ### v0.4.4
229
236
 
230
237
  - **The dock contracts stop asking a stylesheet question about JS attributes** — `dockkit.content` and `dockkit.float` were `rule` checks ("does a host stylesheet mention this literal?"). The host sets both attributes from JavaScript — `data-dockkit-content` on a tab host that has a tab, `data-dockkit-float` on a pane the user has floated — and never writes a CSS rule for either, so that question could only ever be answered "no". Both are now **presence** checks, guarded by the host's own tab marker (`data-dockkit-tab`) and by float mode respectively, so a dock with no tab open reads as *n/a* rather than as a rename. `dockkit.empty` — the empty seat the host renders in place of a populated tab host — is a presence check too. Verified against a live host: with no tab open `data-dockkit-tab`/`data-dockkit-content` are absent; clicking the dock's `+` makes both appear (tab 0→1, content 0→1, empty 1→0), and the checks then answer instead of skipping.
package/README.zh.md CHANGED
@@ -225,6 +225,13 @@ pnpm test # 约定表、实时探测逻辑、界面样式表、版本下
225
225
 
226
226
  ## 近期优化
227
227
 
228
+ ### v0.4.5
229
+
230
+ - **`optionalWhen` 终于按它名字的意思工作了** — 这个字段的文档写着「只有当这块界面在屏幕上时才检查」,但探测实现的是**反过来的**:锚点**不在** DOM 里时反而报 *未观察*,锚点**在**时才会去报失效。于是三条停靠约定里的两条、以及两条对话视图约定,全都在检查错误的状态 —— 这就是健康的停靠栏会报出「停靠空位 失效 / no element matches `[data-dockkit-empty]`」的原因。现在这个辅助函数把锚点当作**正向断言**来读,并且把所有锚点改名成它们真正的含义(`dockHasTab`、`dockHasNoTab`、`chatHasMessages`、`paneFloated` …),这样「用缺席来命名」的锚点再也不可能悄悄把检查反过来。四个测试把由此得到的状态表两个方向都钉住了。
231
+ - **关于消息列的约定不再对着「还没有任何消息」的会话开火** — `[data-chat-flow]` 只在会话**有消息**时才渲染,所以它不在,恰恰是全新会话(宿主的 `data-content-phase="hero"` 首屏)和轨迹页的正常状态。原来的锚点是「对话滚动区存在」,而这三个状态下它都存在,于是检查在首屏和**轨迹页**都报了「属性改名」—— 也就是面板上你看到的第二个失效。现在锚点改用 `[data-chat-turn]`,宿主给每条消息行打的标记,这是唯一「恰好只在消息列该存在时存在」的东西。实测:首屏 → 消息列 0 / 消息行 0;发一条消息后 → 1 / 4;轨迹页 → 0 / 0。
232
+ - **`visible` —— 「存在」和「在屏幕上」不是同一个问题** — 标记了 `visible: true` 的选择器检查现在要求元素真的在屏幕上,于是宿主用 `visibility:hidden` 停放在一边的元素,无法满足一条关于「用户正在看的界面」的约定。停靠空位**故意不标记**:宿主在开合 tab 时一直把它渲染着并隐藏,去问它可不可见,就是在问一个宿主两个方向都在用的状态。
233
+ - **再写死一条锚点规则** — 锚点必须命名「这个属性**预期出现**的状态」,绝不能是恰好同时出现的邻居。这个功能里四次「误报失效」全都是同一个错误:界面明明挂着、宿主行为也对,只是**没有任何东西**能让那个属性落上去。
234
+
228
235
  ### v0.4.4
229
236
 
230
237
  - **停靠相关的约定不再拿「样式表问题」去问 JS 属性** — `dockkit.content` 与 `dockkit.float` 原本是 `rule` 检查(「宿主样式表里有没有提到这个字面量」)。但这两个属性都是宿主从 **JavaScript** 打上去的:`data-dockkit-content` 只在**有 tab** 的停靠页宿主上,`data-dockkit-float` 只在用户真的把面板浮动起来时;宿主从来没为它们写过任何 CSS 规则。所以那个问题无论宿主健不健康都只会回答「没有」。现在两条都改成**存在性**检查,分别用宿主自己的 tab 标记(`data-dockkit-tab`)和浮动态做守卫,于是「没开 tab 的停靠」会报成 *未观察*,而不是「属性改名」。`dockkit.empty`(宿主在「有 tab」时用来替换空位的那个座位)同样改成存在性检查。已在真实宿主上验证:没开 tab 时 `data-dockkit-tab`/`data-dockkit-content` 都不存在;点一下停靠栏的 `+`,两者同时出现(tab 0→1、content 0→1、empty 1→0),此时检查会给出结论而不是跳过。
package/lib/client.js CHANGED
@@ -2673,7 +2673,8 @@ window.__ModuleLoader__.load({
2673
2673
  checks: [{
2674
2674
  kind: "selector",
2675
2675
  selector: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
2676
- optionalWhen: "settingsClosed"
2676
+ optionalWhen: "settingsOpen",
2677
+ visible: true
2677
2678
  }],
2678
2679
  sources: [{
2679
2680
  path: "dsh-client-ui-primitives/lib/index.js",
@@ -2729,7 +2730,7 @@ window.__ModuleLoader__.load({
2729
2730
  checks: [{
2730
2731
  kind: "attr",
2731
2732
  attr: "data-composer-card",
2732
- optionalWhen: "chatNotMounted"
2733
+ optionalWhen: "chatHasMessages"
2733
2734
  }],
2734
2735
  sources: [{
2735
2736
  path: "dsh-client-ui-conversation/lib/client.js",
@@ -2755,7 +2756,7 @@ window.__ModuleLoader__.load({
2755
2756
  checks: [{
2756
2757
  kind: "attr",
2757
2758
  attr: "data-cordis-panel",
2758
- optionalWhen: "cordisClosed"
2759
+ optionalWhen: "cordisMounted"
2759
2760
  }],
2760
2761
  sources: [{
2761
2762
  path: "dsh-client-ui-cordis/lib/client.js",
@@ -2781,7 +2782,8 @@ window.__ModuleLoader__.load({
2781
2782
  checks: [{
2782
2783
  kind: "selector",
2783
2784
  selector: "div[data-sidebar-right-panel]",
2784
- optionalWhen: "noRightPanel"
2785
+ optionalWhen: "rightPanelMounted",
2786
+ visible: true
2785
2787
  }],
2786
2788
  sources: [{
2787
2789
  path: `${SIDEBAR_RIGHT}/lib/client.js`,
@@ -2833,7 +2835,8 @@ window.__ModuleLoader__.load({
2833
2835
  checks: [{
2834
2836
  kind: "selector",
2835
2837
  selector: "[data-dockkit-content]",
2836
- optionalWhen: "noDockTab"
2838
+ optionalWhen: "dockHasTab",
2839
+ visible: true
2837
2840
  }],
2838
2841
  sources: [{
2839
2842
  path: ".",
@@ -2860,7 +2863,7 @@ window.__ModuleLoader__.load({
2860
2863
  checks: [{
2861
2864
  kind: "selector",
2862
2865
  selector: "[data-dockkit-empty]",
2863
- optionalWhen: "noDockTab"
2866
+ optionalWhen: "dockHasNoTab"
2864
2867
  }],
2865
2868
  sources: [{
2866
2869
  path: `${SIDEBAR_RIGHT}/lib/client.js`,
@@ -2886,7 +2889,7 @@ window.__ModuleLoader__.load({
2886
2889
  checks: [{
2887
2890
  kind: "selector",
2888
2891
  selector: "[data-dockkit-float]",
2889
- optionalWhen: "noFloatPane"
2892
+ optionalWhen: "paneFloated"
2890
2893
  }],
2891
2894
  sources: [{
2892
2895
  path: `${SIDEBAR_RIGHT}/lib/client.js`,
@@ -2912,7 +2915,8 @@ window.__ModuleLoader__.load({
2912
2915
  checks: [{
2913
2916
  kind: "selector",
2914
2917
  selector: "[data-chat-flow]",
2915
- optionalWhen: "chatNotMounted"
2918
+ optionalWhen: "chatHasMessages",
2919
+ visible: true
2916
2920
  }],
2917
2921
  sources: [{
2918
2922
  path: "dsh-client-ui-chat/lib/client.js",
@@ -2938,7 +2942,7 @@ window.__ModuleLoader__.load({
2938
2942
  checks: [{
2939
2943
  kind: "selector",
2940
2944
  selector: "[data-conversation-scroll]",
2941
- optionalWhen: "chatNotMounted"
2945
+ optionalWhen: "chatHasMessages"
2942
2946
  }],
2943
2947
  sources: [{
2944
2948
  path: "dsh-client-ui-conversation/lib/client.js",
@@ -2964,7 +2968,7 @@ window.__ModuleLoader__.load({
2964
2968
  checks: [{
2965
2969
  kind: "selector",
2966
2970
  selector: "[data-conversation-composer-overlay]",
2967
- optionalWhen: "trajectoryNotMounted"
2971
+ optionalWhen: "trajectoryMounted"
2968
2972
  }],
2969
2973
  sources: [{
2970
2974
  path: "dsh-client-ui-trajectory/lib/client.js",
@@ -3343,26 +3347,43 @@ window.__ModuleLoader__.load({
3343
3347
  if (host === null) return "";
3344
3348
  return getComputedStyle(host).getPropertyValue(token).trim();
3345
3349
  },
3350
+ visible: (el) => {
3351
+ if (el === null || typeof getComputedStyle !== "function") return true;
3352
+ try {
3353
+ return getComputedStyle(el).visibility !== "hidden";
3354
+ } catch {
3355
+ return true;
3356
+ }
3357
+ },
3346
3358
  styleSheets: () => typeof document === "undefined" ? [] : Array.from(document.styleSheets),
3347
3359
  ctx
3348
3360
  };
3349
3361
  }
3350
3362
  const ANCHORS = {
3351
- settingsClosed: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
3352
- chatNotMounted: "[data-chat-flow],[data-conversation-scroll]",
3353
- trajectoryNotMounted: "[data-conversation-composer-overlay]",
3354
- noRightPanel: "div[data-sidebar-right-panel],[data-dockkit-strip],[data-dockkit-pane]",
3355
- cordisClosed: "[data-cordis-panel]",
3356
- noDockTab: "[data-dockkit-tab]",
3357
- noFloatPane: "[data-dockkit-float],[data-sidebar-right-mode=\"float\"]"
3363
+ chatHasMessages: "[data-chat-turn]",
3364
+ trajectoryMounted: "[data-conversation-composer-overlay]",
3365
+ rightPanelMounted: "div[data-sidebar-right-panel],[data-dockkit-strip],[data-dockkit-pane]",
3366
+ cordisMounted: "[data-cordis-panel]",
3367
+ dockHasTab: "[data-dockkit-tab]",
3368
+ dockHasNoTab: "[data-dockkit-empty],[data-dockkit-add-tab]",
3369
+ paneFloated: "[data-dockkit-float],[data-sidebar-right-mode=\"float\"]",
3370
+ settingsOpen: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]"
3358
3371
  };
3359
- function mounted(env, anchorId) {
3372
+ /**
3373
+ * Whether an anchor's surface is on screen.
3374
+ *
3375
+ * `optionalWhen: X` skips the check while this is FALSE — "the feature is not
3376
+ * observable right now", which is what the field name promises. Every anchor is
3377
+ * therefore written as a POSITIVE predicate (`dockHasTab`, `chatHasMessages`), and
3378
+ * an anchor whose name reads as an absence would silently invert its check.
3379
+ */
3380
+ function anchorMounted(env, anchorId) {
3360
3381
  const sel = ANCHORS[anchorId];
3361
3382
  if (sel === void 0) return true;
3362
3383
  try {
3363
3384
  return env.querySelector(sel) !== null;
3364
3385
  } catch {
3365
- return false;
3386
+ return true;
3366
3387
  }
3367
3388
  }
3368
3389
  /** Walk a dotted path off a root object, tolerating a missing first hop. */
@@ -3426,7 +3447,9 @@ window.__ModuleLoader__.load({
3426
3447
  }
3427
3448
  return false;
3428
3449
  }
3429
- /** Which sides of stylesheet the probe can actually read (a blind check must not fail). */
3450
+ /**
3451
+ * Which sides of stylesheet the probe can actually read (a blind check must not fail).
3452
+ */
3430
3453
  function readability(env) {
3431
3454
  const out = {
3432
3455
  host: false,
@@ -3506,14 +3529,14 @@ window.__ModuleLoader__.load({
3506
3529
  };
3507
3530
  }
3508
3531
  case "selector": {
3509
- const present = env.querySelector(check.selector) !== null;
3532
+ const el = env.querySelector(check.selector);
3510
3533
  const name = `querySelector(${JSON.stringify(check.selector)})`;
3511
- if (present) return {
3534
+ if (el !== null && (check.visible !== true || env.visible(el))) return {
3512
3535
  status: "pass",
3513
3536
  name,
3514
3537
  detail: "element found"
3515
3538
  };
3516
- if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3539
+ if (check.optionalWhen !== void 0 && !anchorMounted(env, check.optionalWhen)) return {
3517
3540
  status: "skip",
3518
3541
  name,
3519
3542
  detail: "not on screen right now"
@@ -3521,8 +3544,8 @@ window.__ModuleLoader__.load({
3521
3544
  return {
3522
3545
  status: "fail",
3523
3546
  name,
3524
- detail: "no element matches",
3525
- reason: `no element matches ${check.selector} while its surface is on screen`
3547
+ detail: el === null ? "no element matches" : "the element is hidden",
3548
+ reason: el === null ? `no element matches ${check.selector} while its surface is on screen` : `${check.selector} is in the DOM but hidden while its surface is on screen`
3526
3549
  };
3527
3550
  }
3528
3551
  case "attr": {
@@ -3533,7 +3556,7 @@ window.__ModuleLoader__.load({
3533
3556
  name,
3534
3557
  detail: "attribute found"
3535
3558
  };
3536
- if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3559
+ if (check.optionalWhen !== void 0 && !anchorMounted(env, check.optionalWhen)) return {
3537
3560
  status: "skip",
3538
3561
  name,
3539
3562
  detail: "not on screen right now"
@@ -3695,7 +3718,7 @@ window.__ModuleLoader__.load({
3695
3718
  //#endregion
3696
3719
  //#region src/client/build-info.ts
3697
3720
  /** This plugin's version at build time ('0.0.0' when built unbundled). */
3698
- const PLUGIN_VERSION = "0.4.4";
3721
+ const PLUGIN_VERSION = "0.4.5";
3699
3722
  /** The dsh floor this build declares, straight from `engines.dsh`. */
3700
3723
  const DSH_FLOOR = "0.1.7-rc.2";
3701
3724
  //#endregion