dsh-theme-endfield 1.2.4 → 1.2.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
@@ -68,6 +68,8 @@ dsh plugin --profile web rm dsh-theme-endfield
68
68
  - **插件卡片展示文案**改由 `locale/<语言>.json` 的 `meta.title` / `meta.description` 提供;`package.json` 的 `dsh.client.inject` 也不再列 0.2 已删除的 `@deepseek-ai/dsh-client-runtime` 与 `@deepseek-ai/dsh-client-ui-slots`,改为列出 0.2 真正的提供方。
69
69
  - **composer seat 的 `will-change: transform`**(0.2.2-rc.1):它会让 seat 成为 `position: fixed` 后代的 containing block,而审批白条正是靠 fixed 铺满视口——白条因此塌成窗口底部的一条线,只剩「等待审批」可见([#41](https://github.com/ymh0000123/dsh-theme-endfield/issues/41))。主题在审批面板挂载期间把该提示降回 `auto`,不改 DOM、也不长期关掉上游的合成提示。
70
70
 
71
+ - **工作区会话行高亮两侧留缝**:app 原生高亮是零几何结构——每行自带 3px 透明描边框,选中态只换背景色;原生淡色下内缩不明显,主题的强高亮色让未上色的描边框透出栏底。主题照原生的结构只换颜色——把描边框刷成同一个高亮色,高亮贴满行盒,文字、栏距、滚动条、圆角(圆角模式 12px)全部保持 app 原生,不再触碰滚动容器的裁剪边界。
72
+
71
73
  细节见 [docs/engineering-notes.md](docs/engineering-notes.md)。
72
74
 
73
75
  ### 开关总是「刷新后复位」
package/client.js CHANGED
@@ -6645,6 +6645,7 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
6645
6645
  [class$='_sidebarCol'] [class*='_projectRow']:hover,
6646
6646
  [class$='_sidebarCol'] [class*='_sessionRow']:hover,
6647
6647
  [class$='_sidebarCol'] [class*='_sessionRow'][class*='_selected'],
6648
+ [class$='_sidebarCol'] [class*='_sessionRow'][class*='_menuOpen'],
6648
6649
  [class$='_sidebarCol'] [class*='_searchResultRow']:hover,
6649
6650
  [class$='_sidebarCol'] [class*='_searchResultRow'][class*='_selected'] {
6650
6651
  background: rgba(var(--edge-accent-rgb), 0.22) !important;
@@ -6669,10 +6670,50 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
6669
6670
  body[data-ds-dark-theme] [class$='_sidebarCol'] [class*='_projectRow']:hover,
6670
6671
  body[data-ds-dark-theme] [class$='_sidebarCol'] [class*='_sessionRow']:hover,
6671
6672
  body[data-ds-dark-theme] [class$='_sidebarCol'] [class*='_sessionRow'][class*='_selected'],
6673
+ body[data-ds-dark-theme] [class$='_sidebarCol'] [class*='_sessionRow'][class*='_menuOpen'],
6672
6674
  body[data-ds-dark-theme] [class$='_sidebarCol'] [class*='_searchResultRow']:hover,
6673
6675
  body[data-ds-dark-theme] [class$='_sidebarCol'] [class*='_searchResultRow'][class*='_selected'] {
6674
6676
  background: var(--edge-accent) !important;
6675
6677
  }
6678
+ /* ---------- 会话行高亮:沿用 app 原生描边结构,只换颜色 ----------
6679
+ Reversed from the shipped bundle (dsh-client-ui-workspace
6680
+ Rows.module.css, read back from the installed 0.2.2-rc.1 app.asar):
6681
+ the app draws the hover/selected wash with NO geometry of its own —
6682
+ every row carries
6683
+ border-inline: var(--dsh-sidebar-paint-inset) solid transparent;
6684
+ background-clip: padding-box;
6685
+ (3px) and the selected state is literally one declaration,
6686
+ .wnFoaW_sessionRow.wnFoaW_selected { background-color: ... }.
6687
+ The 3px transparent frame is the app's own design: with its faint
6688
+ native wash the inset reads as intentional, but the theme's strong
6689
+ accent lets the unpainted frame show the column background through on
6690
+ both sides (「两边颜色不是填满的」). So the fix stays INSIDE the app's
6691
+ structure and only changes colors: the frame is painted with the same
6692
+ wash as the background (border-inline-color), which fills the row's
6693
+ border box edge to edge. Nothing moves — rail padding, row padding,
6694
+ the scrollbar gutter, the title's 36px line all stay exactly where the
6695
+ app put them — and because the whole wash now lives inside the row's
6696
+ border box, it can never cross the scroll container's clip region:
6697
+ 圆角模式 keeps the row's native var(--dsw-radius-md) rounding on both
6698
+ ends, and the square-mode blanket pass squares it, both without ever
6699
+ being cut (the previous full-bleed attempt that re-architected the
6700
+ list's margins/clip was reverted for exactly that fragility).
6701
+ Same three row suffixes the theme already paints; the app's menuOpen
6702
+ state is included so its longhand wash fills the frame too. */
6703
+ [class$='_sidebarCol'] :is([class*='_sessionRow'], [class*='_projectRow'], [class*='_searchResultRow']):is(
6704
+ :hover,
6705
+ [class*='_selected'],
6706
+ [class*='_menuOpen']
6707
+ ) {
6708
+ border-inline-color: rgba(var(--edge-accent-rgb), 0.22) !important;
6709
+ }
6710
+ body[data-ds-dark-theme] [class$='_sidebarCol'] :is([class*='_sessionRow'], [class*='_projectRow'], [class*='_searchResultRow']):is(
6711
+ :hover,
6712
+ [class*='_selected'],
6713
+ [class*='_menuOpen']
6714
+ ) {
6715
+ border-inline-color: var(--edge-accent) !important;
6716
+ }
6676
6717
  /* WHY '_badge' not bare 'badge': app CSS-module badges are '<hash>_badge'
6677
6718
  (zf92ZW_badge footer, dlU_AG_badge …), but third-party plugins name
6678
6719
  their own widgets without the underscore word boundary —
@@ -6769,9 +6810,40 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
6769
6810
  (test/approval-band-transform.test.js covers both directions).
6770
6811
  Both anchors are listed because the [class$=] hook dies the moment
6771
6812
  upstream appends a second class to the seat, and [data-composer-seat]
6772
- is the e2e hook the app itself declares. */
6773
- body:has([data-approval-key]) :is([data-composer-seat], [class$='_composerSeat']) {
6813
+ is the e2e hook the app itself declares.
6814
+
6815
+ DEFENCE IN DEPTH (the 自动授权审查 field report). will-change on the
6816
+ seat is the only containing-block producer measured on the seat's
6817
+ ancestor chain in 0.2.2-rc.1 — but the band is position:fixed, so ANY
6818
+ producer on ANY ancestor between it and the viewport re-creates the
6819
+ whole collapse, and the apps that hit it in the field were one release
6820
+ behind (the report shipped before this rule existed). While a panel is
6821
+ mounted the app is modal-blocked anyway, so the theme releases every
6822
+ containing-block-producing property on the seat AND the structural
6823
+ layers the seat actually nests under, all verified in the extracted
6824
+ bundles and all semantic anchors: the conversation scrollport
6825
+ ([data-conversation-scroll]), the conversation root
6826
+ ([data-conversation-content]), the frame and center column (the same
6827
+ suffix hooks the contour sheet already relies on). transform:none is
6828
+ the only declaration that could plausibly change a visible pixel
6829
+ elsewhere, and everything it could un-transform is behind the band's
6830
+ own full-viewport mask for exactly as long as the panel exists. */
6831
+ body:has([data-approval-key]) :is(
6832
+ [data-composer-seat],
6833
+ [class$='_composerSeat'],
6834
+ [data-conversation-scroll],
6835
+ [data-conversation-content],
6836
+ [class$='_centerCol'],
6837
+ [class*='_frame']
6838
+ ) {
6774
6839
  will-change: auto !important;
6840
+ transform: none !important;
6841
+ filter: none !important;
6842
+ backdrop-filter: none !important;
6843
+ perspective: none !important;
6844
+ container-type: normal !important;
6845
+ content-visibility: visible !important;
6846
+ contain: none !important;
6775
6847
  }
6776
6848
  /* ---------- 审批弹窗:全屏白条 + 上下遮罩(Endfield 寻访确认框) ----------
6777
6849
  The shipped panel is a rounded warn-bordered card parked inside the
@@ -6811,8 +6883,14 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
6811
6883
  }
6812
6884
  /* The band: square, full-bleed, paper. The zero-radius pass in round
6813
6885
  mode would restore the app's --dsw-radius-xl here, and a rounded band
6814
- contradicts the reference — pin it flat with priority. */
6815
- [data-approval-key] > [class$='_card'] {
6886
+ contradicts the reference — pin it flat with priority. '_card' as a
6887
+ SUBSTRING, not a suffix: subject-bearing approvals (an agent-team
6888
+ escalation renders its icon + name) ship the card as
6889
+ '<hash>_card <hash>_withSubject', and a $= match dies the moment that
6890
+ second class is appended — the band would keep its mask but lose the
6891
+ paper. The root's ONLY child is the card, so the looser hook cannot
6892
+ reach anything else. */
6893
+ [data-approval-key] > [class*='_card'] {
6816
6894
  width: 100%;
6817
6895
  max-width: none !important;
6818
6896
  box-sizing: border-box;
@@ -1021,6 +1021,31 @@ body:has([data-approval-key]) :is([data-composer-seat], [class$='_composerSeat']
1021
1021
 
1022
1022
  这同时解释了为什么**报告者给的补丁能修好、但会退化**:把 root 改成 `position: relative` + 透明背景,面板回到文档流,功能立刻恢复,但通栏纸带被收进聊天列宽、上下遮罩一并消失——那是「把设计也一起删了」,不是等价修复。修在祖先上,两样都留着。
1023
1023
 
1024
+ ### 十类:兜底按「属性清单」打,不按已知元凶打(自动授权审查的现场报告)
1025
+
1026
+ #41 落地次日,评论区来了第二份白条塌陷报告:开着「自动授权审查」实验性插件,被自动审查拒绝的工具调用要等人工审批,弹窗装上主题后无法显示、无法点击,关主题即恢复。现场截图的形态与 #41 分毫不差——底部只活着一行「● 等待审批」。
1027
+
1028
+ 对 0.2.2-rc.1 的安装包逐层核对(ui-approval / ui-conversation / ui-session 的 bundle)后确认:**自动审查走的就是同一条 `approval/request` 瀑布、同一个 composer 座位里的同一个面板**,座位祖先链上除 `will-change` 外也没有第二个元凶。真正能解释现场的是版本:报告者装主题的时间在 v1.2.4 发布之前,手里还是没有 #41 的旧构建。但这条线索指出了 #41 修法的结构性弱点——它只按「已知元凶」释放了 `will-change` 一个属性、座位一个元素,而白条塌陷的充分条件是**任意祖先的任意致产属性**。上游任何一次重构(把 transform 挪到包裹层、给滚动层加 container-type)都会让同一条塌陷回来。
1029
+
1030
+ 所以兜底改成按属性清单打:面板在屏期间(`body:has([data-approval-key])`),对座位和它已核实的结构层——`[data-conversation-scroll]`(会话滚动体,JSX 实测属性)、`[data-conversation-content]`(会话根,同)、`_centerCol` / `_frame`(与等高线图纸共用的后缀钩子)——一并释放全部致产属性:`transform`、`filter`、`backdrop-filter`、`perspective`、`container-type`、`content-visibility`、`contain`。这些层在 0.2.2-rc.1 上都没有致产值,规则今天是空操作;代价只存在于「面板在屏」的帧里,而那时应用正被白条遮罩挡着。`test/approval-band-transform.test.js` 为此加了两条白条:座位干净、祖先分别带 `container-type: inline-size` 与 `transform`,配合仍会塌陷的对照组,证明释放真的生效而不是「全页没陷阱」的假阳性。附带修掉一个顺藤摸出来的真钩子:带主体(图标 + 名称)的审批卡类名是 `<hash>_card <hash>_withSubject`,`[class$='_card']` 在第二个类 appended 后静默失配——卡片钩子放宽为 `[class*='_card']`(root 的唯一子元素就是卡,不可能误中)。
1031
+
1032
+ **教训**:防御性规则要么按故障的充分条件枚举(所有致产属性 × 全部结构层),要么干脆别写;只覆盖已知名元的兜底给后来者一种「已经防住了」的错觉,而塌陷的充分条件集合在上游任何一次重构后都会变大。
1033
+
1034
+ ### 十一类:高亮不满宽是三层内缩的算术(工作区会话行)
1035
+
1036
+ 评论区另一份独立报告:左侧工作区的会话行,选中 / 悬停的强调色洗不满行盒,「两边颜色不是填满的」。对照 0.2.2-rc.1 的 bundle 把行盒到侧栏两缘之间的层全数核出,一共三层:
1037
+
1038
+ 1. **行自带 3px 内缩**——`.*_projectRow/. *_sessionRow` 携带 `border-inline: var(--dsh-sidebar-paint-inset) solid transparent`(3px)+ `background-clip: padding-box`,洗色永远画不到描边下面。主题自己的 `background:` 简写会把 clip 重置回 border-box,所以**选中和悬停其实早就贴到了行盒边缘**;没贴边的观感来自下面两层(以及 app 用 longhand 画的 menuOpen 态)。
1039
+ 2. **侧栏栏根 8px 内边距**——SidebarRoot 的根节点 `padding: 6px var(--dsh-sidebar-inline-padding)`,行盒从 8px 处才开始。
1040
+ 3. **列表右侧滚动条槽**——`.*_list` 的 `margin-right: var(--dsh-session-list-scrollbar-offset)` + `padding-right: calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset))`,行盒右缘距列缘还差 `inline-padding + edge-inset − scrollbar-width`。
1041
+
1042
+ 前两版修法都在重排几何、都被打回。第一版把负外边距打在**行**上——几何测试全绿(行盒 rect [0, 232]),现场却是「圆角模式下高亮还是直角」:`getBoundingClientRect` 量不出**裁剪**,会话列表是滚动容器(`overflow-y: auto` 使 `overflow-x` 计算为 auto),在**自己的 padding box** 处裁剪绘制与命中测试,圆角模式(app 原生 12px 圆角)的两端圆弧被齐齐切平,还顺带获得一段横向可拖拽区。第二版把外扩挪到列表上(负 margin + padding 归零 + 全套内层补偿)圆角是保住了,但整座补偿算术(骨架行、空态、显示更多按钮、rail 变体的溢出裁剪、隐藏滚动条)都悬在「裁剪边界恰好等于列缘」这一根独苗上。探针还接连否决了两条「聪明」路:`overflow-clip-margin` 只在元素该轴本身是 `clip` 时才被 Chromium 认(与滚动容器轴混用一律忽略,实测),滚动上移到 listArea 则把坑原样搬到上一层。
1043
+
1044
+ **终版(作者裁定:「直接逆向一下,看下原本是怎么做的,在原本基础上换颜色算了」)回到原生的机制。** 逆向结论:原生高亮**零几何**——每行自带 3px 透明描边框 + `background-clip: padding-box`,选中态就一句 `background-color` 换色;原生淡色下 3px 内缩读作有意的胶囊设计,主题的强高亮色一刷,未上色的描边框把栏底透出来,这才是「两边不满」的显形来源。于是修法留在原生结构内、只动颜色:`border-inline-color` 刷成同一个高亮色,高亮即贴满行盒——栏内边距、滚动条槽、标题线(8+3+9+16=36px)、`--dsh-workspace-indent` 算术全部原样,圆角随原生 border-box 走(圆角模式 12px、方角模式被 blanket pass 归零),绘制全部发生在行盒内、滚动容器裁剪区内,**裁剪问题被结构性地排除而不是被对抗**。app 的 menuOpen 态(longhand 换色)一并纳入主题换色,避免「描边主题色 + 内部原生灰」的割裂。`test/sidebar-row-wash.test.js` 的断言策略是**双 boot 对照**:主题关闭先量原生基线,圆角 / 方角两个主题态再量一次,几何(行左缘 / 行高 / 标题线 / 列表盒与全部 CSS 声明)逐值一致——「换颜色不动几何」的可执行定义;颜色断言描边色 === 背景色,圆角断言 12px 保留 / 归零。
1045
+
1046
+ 顺带钉死一条 Blink 怪癖(对照测试差点被它假失配搞挂):**`::-webkit-scrollbar` 的级联不按特异性、不按 `!important`、也不可被后插入的样式表翻转**——页面 `#list::-webkit-scrollbar{width:5px !important}` 输给后注入的主题全局 `::-webkit-scrollbar{width:10px}`,boot 之后再 append 一条 5px 全局规则同样翻不掉(实测 gap 恒 10px)。主题的全局滚动条规则一旦注入就统治文档的全部滚动容器占位,这也解释了真实 app 中列表占位是 10px(app 自己的 `--dsh-session-list-scrollbar-width` 计划值是 5px)——它是主题 v1.2.4 起的既成行为。对照测试因此不跨 boot 比行宽(占位差会假失配),只比滚动条碰不到的左半边,行宽改用列表盒内部一致性断言(行右缘 = 列表 client 右缘 − 右内边距)。
1047
+
1048
+
1024
1049
  ---
1025
1050
 
1026
1051
  ## 验证方法论
package/docs/features.md CHANGED
@@ -286,10 +286,13 @@
286
286
  | 会话头部预设徽章(agent preset,如「创造模式」) | 上游把徽章移进 `_headerActions` 插槽的包裹层里,主题的 `>` 选择器失配,退回默认灰胶囊 | 恢复强调色填充 + 黑字,**尺寸沿用上游**(不撑宽) |
287
287
  | 审批弹窗(「等待审批」) | 上游是塞在输入区里的一张圆角描边小卡 | 重绘为**贯穿屏幕左右白条 + 上下遮罩**的居中确认框,按钮改为胶囊(拒绝=深色+✕,允许一次=信号黄+◎),见下 |
288
288
  | 完全权限确认框(「确认启用完全权限?」) | 共享 Modal 的居中圆角小卡 | 同款白条 + 上下遮罩,取消/启用胶囊按钮,见下 |
289
+ | 工作区会话行高亮(hover / 选中) | 原生高亮零几何:每行自带 3px 透明描边框(`border-inline` + `background-clip: padding-box`),选中态只换一句 `background-color`;原生淡色下内缩读作有意设计,主题的强高亮色一刷,未上色的描边框把栏底透出来,两侧显缝(「两边颜色不是填满的」) | 在原生结构内只换颜色:`border-inline-color` 刷成同一个高亮色,高亮贴满行盒;栏内边距、滚动条槽、文字线全部保持 app 原生不动,圆角模式保留原生 12px 圆角、方角模式直角;绘制全部在行盒内、滚动容器裁剪区内,圆角不再被切;menuOpen 态一并换色,见下 |
290
+ | 审批白条 containing block 兜底 | #41 只释放了输入区座位的 `will-change`;实测 0.2.2-rc.1 里座位祖先链没有别的元凶,但「自动授权审查」的用户报告证明这类塌陷会真实发生 | 面板挂载期间对座位**及其已核实的结构层**(`[data-conversation-scroll]`、`[data-conversation-content]`、`_centerCol`、`_frame`)一并释放全部产生 containing block 的属性(transform / filter / backdrop-filter / perspective / container-type / content-visibility / contain);附带修复:带主体(图标+名称)的审批卡类名是 `<hash>_card <hash>_withSubject`,卡片钩子从 `$=` 放宽为 `*=` 才能命中 |
289
291
 
290
292
  ### 审批弹窗:全屏白条 + 上下遮罩
291
293
 
292
294
  工具越权执行时弹出的 `[data-approval-key]` 面板被重绘成参考图那种形态:**不透明纸色横条从屏幕左缘贯到右缘、垂直居中,页面其余部分只有上下被压暗**——实现上没有加任何 DOM:面板自己的 root 变成 fixed 全屏 flex 层,它的半透明黑底就是遮罩,中间的白条不透明地盖住横带,遮罩自然只剩上下两截。文字居中,「等待审批」保留状态点但去掉警示底色。两个按钮改成终末地胶囊(左取消深灰配白 ✕、右确认信号黄配 ◎,墨字对比度与主题其它实心芯片同一条 AA 约束)。**圆角模式同样保持胶囊**(与余额药丸一致),且所有选择器都钉在语义锚点上(`data-approval-key` + `[class*='_card']` 等 CSS-module 后缀),上游重哈希不会失效——`test/selector-guard.test.js` 钉住这三个钩子。
295
+ **挂载期间的 containing block 兜底(#41 的延伸)**:白条是 `position: fixed`,座位到视口之间的**任何**产生 containing block 的祖先都会把白条重新压扁成一条线(#41 的实测就是输入区座位的 `will-change: transform`)。面板在屏时应用本来就处于模态阻塞,主题因此同时释放座位与已核实结构层上的全部致产属性;`test/approval-band-transform.test.js` 用「座位自身干净、祖先带 container-type / transform」的两个白条夹住这条规则——对照组塌陷、这两条满屏,才证明释放真的生效。
293
296
 
294
297
  ### 完全权限确认框:同一形态
295
298
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-endfield",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "DeepSeek Harness web theme: the industrial editorial look of the Arknights: Endfield official site — cream paper, ink text, signal-yellow accents, zero radius. Settings persist through the DSH volatile Config + configForms transport (0.2.0-rc.2 and 0.1.7+), with a ctx.settingsScope fallback for older hosts.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -47,8 +47,8 @@
47
47
  "test:coverage": "node test/contour-coverage.test.js",
48
48
  "test:perf": "node test/contour-perf.test.js",
49
49
  "test:loader": "node test/loader-performance.test.js && node test/loader-late-prefs.test.js",
50
- "test": "node check.js && node selftest.js && node test/import-probe.test.js && node test/font-scope.test.js && node test/palette-contrast.test.js && node test/palette-switch.test.js && node test/menu-surface.test.js dark && node test/menu-surface.test.js light && node test/settings-buttons.test.js && node test/tools/hover-check.js && node test/selector-guard.test.js && node test/preset-chip.test.js && node test/balance-window.test.js && node test/balance-capsule.test.js && node test/credit-capsule.test.js && node test/settings-rows.test.js && node test/settings-durable-hold.test.js && node test/settings-config-forms.test.js && node test/settings-config-fallback.test.js && node test/settings-namespace.test.js && node test/settings-off.test.js && node test/settings-locale.test.js && node test/audio-notify.test.js && node test/audio-attention-watch.test.js && node test/host-no-inject.test.js && node scripts/build-sounds.js --check && node test/thunder-edges.test.js && node test/tools/thunder-shot.js && node test/thunder-dismiss.test.js && node test/contour-render.test.js && node test/loader-performance.test.js && node test/loader-late-prefs.test.js && node test/contour-specks.test.js && node test/watermark-stacking.test.js && node test/contour-smoothness.test.js && node test/contour-cusps.test.js && node test/contour-a11y.test.js && node test/contour-coverage.test.js && node test/contour-perf.test.js && node test/contour-density.test.js && node test/contour-bounds.test.js && node scripts/build-host.js --check && node scripts/build-client.js --check && node scripts/build-contour-worker.js --check && node test/worker-eol.test.js && node test/contour-worker.test.js && node test/contour-worker-budget.test.js && node test/theme-sheet-refresh.test.js && node test/titlebar-probe.test.js && node test/corner-shape.test.js && node test/approval-band-transform.test.js && node test/glass.test.js && node test/contour-trail.test.js && node test/contour-trail-browser.test.js && node test/scroll-anim.test.js && node test/contour-loop-reentry.test.js",
51
- "test:ci": "node check.js && node selftest.js && node test/import-probe.test.js && node test/font-scope.test.js && node test/palette-contrast.test.js && node test/palette-switch.test.js && node test/menu-surface.test.js dark && node test/menu-surface.test.js light && node test/settings-buttons.test.js && node test/tools/hover-check.js && node test/selector-guard.test.js && node test/preset-chip.test.js && node test/balance-window.test.js && node test/balance-capsule.test.js && node test/credit-capsule.test.js && node test/settings-rows.test.js && node test/settings-durable-hold.test.js && node test/settings-config-forms.test.js && node test/settings-config-fallback.test.js && node test/settings-namespace.test.js && node test/settings-off.test.js && node test/settings-locale.test.js && node test/audio-notify.test.js && node test/audio-attention-watch.test.js && node test/host-no-inject.test.js && node scripts/build-sounds.js --check && node test/thunder-edges.test.js && node test/tools/thunder-shot.js && node test/thunder-dismiss.test.js && node test/contour-render.test.js && node test/loader-performance.test.js && node test/loader-late-prefs.test.js && node test/contour-specks.test.js && node test/watermark-stacking.test.js && node test/contour-smoothness.test.js && node test/contour-cusps.test.js && node test/contour-a11y.test.js && node test/contour-density.test.js && node test/contour-bounds.test.js && node scripts/build-host.js --check && node scripts/build-client.js --check && node scripts/build-contour-worker.js --check && node test/worker-eol.test.js && node test/contour-worker.test.js && node test/contour-worker-budget.test.js && node test/theme-sheet-refresh.test.js && node test/titlebar-probe.test.js && node test/corner-shape.test.js && node test/approval-band-transform.test.js && node test/glass.test.js && node test/contour-trail.test.js && node test/contour-trail-browser.test.js && node test/scroll-anim.test.js && node test/contour-loop-reentry.test.js",
50
+ "test": "node check.js && node selftest.js && node test/import-probe.test.js && node test/font-scope.test.js && node test/palette-contrast.test.js && node test/palette-switch.test.js && node test/menu-surface.test.js dark && node test/menu-surface.test.js light && node test/settings-buttons.test.js && node test/tools/hover-check.js && node test/selector-guard.test.js && node test/preset-chip.test.js && node test/balance-window.test.js && node test/balance-capsule.test.js && node test/credit-capsule.test.js && node test/settings-rows.test.js && node test/settings-durable-hold.test.js && node test/settings-config-forms.test.js && node test/settings-config-fallback.test.js && node test/settings-namespace.test.js && node test/settings-off.test.js && node test/settings-locale.test.js && node test/audio-notify.test.js && node test/audio-attention-watch.test.js && node test/host-no-inject.test.js && node scripts/build-sounds.js --check && node test/thunder-edges.test.js && node test/tools/thunder-shot.js && node test/thunder-dismiss.test.js && node test/contour-render.test.js && node test/loader-performance.test.js && node test/loader-late-prefs.test.js && node test/contour-specks.test.js && node test/watermark-stacking.test.js && node test/contour-smoothness.test.js && node test/contour-cusps.test.js && node test/contour-a11y.test.js && node test/contour-coverage.test.js && node test/contour-perf.test.js && node test/contour-density.test.js && node test/contour-bounds.test.js && node scripts/build-host.js --check && node scripts/build-client.js --check && node scripts/build-contour-worker.js --check && node test/worker-eol.test.js && node test/contour-worker.test.js && node test/contour-worker-budget.test.js && node test/theme-sheet-refresh.test.js && node test/titlebar-probe.test.js && node test/corner-shape.test.js && node test/approval-band-transform.test.js && node test/sidebar-row-wash.test.js && node test/glass.test.js && node test/contour-trail.test.js && node test/contour-trail-browser.test.js && node test/scroll-anim.test.js && node test/contour-loop-reentry.test.js",
51
+ "test:ci": "node check.js && node selftest.js && node test/import-probe.test.js && node test/font-scope.test.js && node test/palette-contrast.test.js && node test/palette-switch.test.js && node test/menu-surface.test.js dark && node test/menu-surface.test.js light && node test/settings-buttons.test.js && node test/tools/hover-check.js && node test/selector-guard.test.js && node test/preset-chip.test.js && node test/balance-window.test.js && node test/balance-capsule.test.js && node test/credit-capsule.test.js && node test/settings-rows.test.js && node test/settings-durable-hold.test.js && node test/settings-config-forms.test.js && node test/settings-config-fallback.test.js && node test/settings-namespace.test.js && node test/settings-off.test.js && node test/settings-locale.test.js && node test/audio-notify.test.js && node test/audio-attention-watch.test.js && node test/host-no-inject.test.js && node scripts/build-sounds.js --check && node test/thunder-edges.test.js && node test/tools/thunder-shot.js && node test/thunder-dismiss.test.js && node test/contour-render.test.js && node test/loader-performance.test.js && node test/loader-late-prefs.test.js && node test/contour-specks.test.js && node test/watermark-stacking.test.js && node test/contour-smoothness.test.js && node test/contour-cusps.test.js && node test/contour-a11y.test.js && node test/contour-density.test.js && node test/contour-bounds.test.js && node scripts/build-host.js --check && node scripts/build-client.js --check && node scripts/build-contour-worker.js --check && node test/worker-eol.test.js && node test/contour-worker.test.js && node test/contour-worker-budget.test.js && node test/theme-sheet-refresh.test.js && node test/titlebar-probe.test.js && node test/corner-shape.test.js && node test/approval-band-transform.test.js && node test/sidebar-row-wash.test.js && node test/glass.test.js && node test/contour-trail.test.js && node test/contour-trail-browser.test.js && node test/scroll-anim.test.js && node test/contour-loop-reentry.test.js",
52
52
  "shots": "node test/tools/shoot.js",
53
53
  "shots:verify": "node test/tools/shoot.js && node test/tools/verify-shots.js",
54
54
  "test:bounds": "node test/contour-bounds.test.js",