dsh-background-by-model 0.4.3 → 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,18 @@ 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
+
235
+ ### v0.4.4
236
+
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.
238
+ - **Anchors no longer depend on combinators** — The populated-tab anchor started out as `[data-dockkit-strip-tabs] > *`, which a DOM stand-in cannot answer and which let the fixture and the browser disagree. It is now a plain attribute selector.
239
+
228
240
  ### v0.4.3
229
241
 
230
242
  - **The Host check tab stops crying wolf** — Its first real run on a healthy 0.1.7 install reported **9 failures out of 28 contracts** (7 tokens "no longer declared", 2 attributes gone). Every one was the *probe reading the wrong place*, not the host: the tokens are published as `body{--dsw-…}` while the probe asked `getComputedStyle(document.documentElement)`, and `data-plugin` — which the host's own CSS modules set on every `<style>` they inject — was being used to mean "this plugin's sheet", so the check skipped the entire host theme and then called the tokens renamed. Both are fixed, and both now have tests that fail if they come back: the token root is a named part of `ProbeEnv`, and reading the plugin's own sheets goes through a marker the plugin writes itself (`data-dab-side`), via `getAttribute` rather than `dataset` — `dataset` only exposes the camel-cased key, so a probe indexing it by the dashed name works in a fixture and silently returns nothing in a browser.
package/README.zh.md CHANGED
@@ -225,6 +225,18 @@ 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
+
235
+ ### v0.4.4
236
+
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),此时检查会给出结论而不是跳过。
238
+ - **锚点不再依赖组合符** — 「有 tab」的锚点最初写成 `[data-dockkit-strip-tabs] > *`,这种写法 DOM 替身回答不了,也让夹具和浏览器悄悄产生分歧。现在改成一个普通属性选择器。
239
+
228
240
  ### v0.4.3
229
241
 
230
242
  - **「宿主自检」不再虚报故障** — 它第一次在健康的 0.1.7 上实跑,28 条约定报了 **9 条失效**(7 个令牌「不再声明」、2 个属性「消失」)。**全部是探测读错了地方,宿主一条都没坏**:设计令牌发布在 `body{--dsw-…}` 上,探测却去问 `getComputedStyle(document.documentElement)`;而 `data-plugin` —— 宿主自己的 CSS 模块在**每一个** `<style>` 上都写它 —— 被我当成了「这是插件自己的样式表」,于是检查跳过了整个宿主主题,再把令牌报成改名。两处都已修正,并且各配了会让它复发的测试:令牌读取的宿主元素成了 `ProbeEnv` 的显式成员;识别自己的样式表改用一个只由本插件写入的标记(`data-dab-side`),且用 `getAttribute` 而不是 `dataset` —— `dataset` 只暴露驼峰键,按带连字符的名字去索引它,在夹具里能通过、在浏览器里静默返回空。
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`,
@@ -2831,9 +2833,10 @@ window.__ModuleLoader__.load({
2831
2833
  },
2832
2834
  target: "[data-dockkit-content]",
2833
2835
  checks: [{
2834
- kind: "rule",
2835
- match: "[data-dockkit-content]",
2836
- hint: "dock tab host"
2836
+ kind: "selector",
2837
+ selector: "[data-dockkit-content]",
2838
+ optionalWhen: "dockHasTab",
2839
+ visible: true
2837
2840
  }],
2838
2841
  sources: [{
2839
2842
  path: ".",
@@ -2858,9 +2861,9 @@ window.__ModuleLoader__.load({
2858
2861
  },
2859
2862
  target: "[data-dockkit-empty]",
2860
2863
  checks: [{
2861
- kind: "rule",
2862
- match: "[data-dockkit-empty]",
2863
- hint: "dock empty seat"
2864
+ kind: "selector",
2865
+ selector: "[data-dockkit-empty]",
2866
+ optionalWhen: "dockHasNoTab"
2864
2867
  }],
2865
2868
  sources: [{
2866
2869
  path: `${SIDEBAR_RIGHT}/lib/client.js`,
@@ -2884,9 +2887,9 @@ window.__ModuleLoader__.load({
2884
2887
  },
2885
2888
  target: "[data-dockkit-float]",
2886
2889
  checks: [{
2887
- kind: "rule",
2888
- match: "[data-dockkit-float]",
2889
- hint: "floating pane flag"
2890
+ kind: "selector",
2891
+ selector: "[data-dockkit-float]",
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,24 +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]"
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]"
3356
3371
  };
3357
- 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) {
3358
3381
  const sel = ANCHORS[anchorId];
3359
3382
  if (sel === void 0) return true;
3360
3383
  try {
3361
3384
  return env.querySelector(sel) !== null;
3362
3385
  } catch {
3363
- return false;
3386
+ return true;
3364
3387
  }
3365
3388
  }
3366
3389
  /** Walk a dotted path off a root object, tolerating a missing first hop. */
@@ -3424,7 +3447,9 @@ window.__ModuleLoader__.load({
3424
3447
  }
3425
3448
  return false;
3426
3449
  }
3427
- /** 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
+ */
3428
3453
  function readability(env) {
3429
3454
  const out = {
3430
3455
  host: false,
@@ -3504,14 +3529,14 @@ window.__ModuleLoader__.load({
3504
3529
  };
3505
3530
  }
3506
3531
  case "selector": {
3507
- const present = env.querySelector(check.selector) !== null;
3532
+ const el = env.querySelector(check.selector);
3508
3533
  const name = `querySelector(${JSON.stringify(check.selector)})`;
3509
- if (present) return {
3534
+ if (el !== null && (check.visible !== true || env.visible(el))) return {
3510
3535
  status: "pass",
3511
3536
  name,
3512
3537
  detail: "element found"
3513
3538
  };
3514
- if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3539
+ if (check.optionalWhen !== void 0 && !anchorMounted(env, check.optionalWhen)) return {
3515
3540
  status: "skip",
3516
3541
  name,
3517
3542
  detail: "not on screen right now"
@@ -3519,8 +3544,8 @@ window.__ModuleLoader__.load({
3519
3544
  return {
3520
3545
  status: "fail",
3521
3546
  name,
3522
- detail: "no element matches",
3523
- 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`
3524
3549
  };
3525
3550
  }
3526
3551
  case "attr": {
@@ -3531,7 +3556,7 @@ window.__ModuleLoader__.load({
3531
3556
  name,
3532
3557
  detail: "attribute found"
3533
3558
  };
3534
- if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3559
+ if (check.optionalWhen !== void 0 && !anchorMounted(env, check.optionalWhen)) return {
3535
3560
  status: "skip",
3536
3561
  name,
3537
3562
  detail: "not on screen right now"
@@ -3693,7 +3718,7 @@ window.__ModuleLoader__.load({
3693
3718
  //#endregion
3694
3719
  //#region src/client/build-info.ts
3695
3720
  /** This plugin's version at build time ('0.0.0' when built unbundled). */
3696
- const PLUGIN_VERSION = "0.4.3";
3721
+ const PLUGIN_VERSION = "0.4.5";
3697
3722
  /** The dsh floor this build declares, straight from `engines.dsh`. */
3698
3723
  const DSH_FLOOR = "0.1.7-rc.2";
3699
3724
  //#endregion