dsh-background-by-model 0.12.2 → 0.12.4

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/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Changelog
2
2
 
3
- > The full release history lives here; the **Recent optimizations** section of [README.md](./README.md#recent-optimizations) lists only the two most recent releases (v0.12.2 / v0.12.1).
3
+ > The full release history lives here; the **Recent optimizations** section of [README.md](./README.md#recent-optimizations) lists only the two most recent releases (v0.12.4 / v0.12.3).
4
+
5
+ ## v0.12.4
6
+
7
+ - **The nav mark is redrawn with a gentler slope, and the sun hangs left of the first peak** — at 16px the steep hills read as a person, and a sun over the summit made the glyph a symmetric emblem rather than the landscape `icon.svg` shows.
8
+ - **The brand tile draws it at 20px, not 17** — inside a 30px tile the old size left the ink covering 35% of the height, a ring of empty colour the eye reads as a mistake; 20 brings it to ~55%.
9
+
10
+ ## v0.12.3
11
+
12
+ - **The settings nav shows this plugin's mark, not the harness's default gear** — the glyph was patched into the mounted DOM cell, and the patch looked for the nav button whose first `<span>` read as the section label. The host's markup puts the icon span first, so that span's text was empty, the label never matched and the patch returned having found nothing. It is now a `settings.section.icon` registration, which is the supported route.
13
+ - **The nav icon and the section share one id** — the icon's registration id must equal the section's (`only: row.id` filters everything else out), so both read it from a single constant instead of two literals that can drift.
14
+ - **A new mark: a sun over hills, the 16px counterpart of the store icon** — drawn on the 16-unit grid rather than scaled down from 36, because at nav size the larger drawing's rounding and its echo cards collapse. `currentColor` throughout, so the sidebar tints it like any other row and the brand tile supplies its own foreground. The plain sun stays, since its other caller is the "follow the system theme" button.
15
+ - **The published icon is 20% larger** — its ink covered 70%×78% of the 36 viewBox, so it sat noticeably smaller in the plugin manager than the other plugins' artwork. Measured against the sidebar strip, ours read 37% of the container width against 51% for a neighbouring plugin; 1.20 brings it to the same weight while staying inside the viewBox (1.277 is the vertical limit).
16
+ - **`check:mark`** — the slot registration, the shared id, the absence of the DOM patch, the mark's `currentColor` and grid, and the artwork's scale staying under its clipping limit. Verified to fail when the id is wrong, when the observer comes back, and when the section names a literal.
4
17
 
5
18
  ## v0.12.2
6
19
 
package/CHANGELOG.zh.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # 更新日志
2
2
 
3
- > 这里保留完整的历史记录;[README.zh.md](./README.zh.md#近期优化) 的「近期优化」一节只列最近两个版本(v0.12.2 / v0.12.1)。
3
+ > 这里保留完整的历史记录;[README.zh.md](./README.zh.md#近期优化) 的「近期优化」一节只列最近两个版本(v0.12.4 / v0.12.3)。
4
+
5
+ ## v0.12.4
6
+
7
+ - **导航标记重画:坡更缓,太阳挪到第一个峰的左边** —— 16px 下原来的陡坡看着像个人,太阳压在峰顶又让整个字形成了对称徽标,而不是 `icon.svg` 里那座山。
8
+ - **品牌方块里的尺寸从 17 改成 20** —— 在 30px 方块里,原来墨迹只占高度的 35%,四周留一圈空,看着像出了错;20 让它占到约 55%。
9
+
10
+ ## v0.12.3
11
+
12
+ - **设置导航里显示的是这个插件自己的标记,不再是宿主的默认齿轮** —— 那个字形是补丁式地塞进已挂载的 DOM 单元里的,它去找"第一个 `<span>` 文字等于分组名"的按钮。而宿主的结构里第一个 span 就是图标那个,文字为空,于是标签永远匹配不上,补丁什么也没找到就返回了。现在改成注册 `settings.section.icon` 插槽,这才是官方支持的方式。
13
+ - **导航图标与分组共用一个 id** —— 图标注册的 id 必须等于分组的 id(`only: row.id` 会把其它的都过滤掉),所以两处都从同一个常量读,而不是两个可能漂移的字面量。
14
+ - **新标记:太阳与山,即商店图标在 16px 网格上的对应画法** —— 直接在 16 单位网格上画,而不是把 36 的缩小,因为导航那个尺寸下大图的圆角和两片残影会糊成一团。整体 `currentColor`,侧栏按文字色着色、品牌方块自带前景色。原来的纯太阳留着,它的另一个调用方是「跟随系统主题」按钮。
15
+ - **发布的图标放大了 20%** —— 原来的墨迹只占 36 视框的 70%×78%,在插件管理器里明显比别的插件小一圈。对着侧栏截图实测,我们只占容器宽度 37%,旁边那个插件是 51%;1.20 让它回到同样的分量,同时仍在视框内(垂直上限是 1.277)。
16
+ - **`check:mark`** —— 插槽注册、共用 id、DOM 补丁不得回归、标记的 `currentColor` 与网格、以及图标的缩放不得超过裁切上限。验证过:id 写错、观察器回归、分组用字面量,三种都会红。
4
17
 
5
18
  ## v0.12.2
6
19
 
package/README.md CHANGED
@@ -237,7 +237,7 @@ To mount a working copy into a profile instead, add it to the profile's `package
237
237
 
238
238
  After changing anything under `src/`, run `pnpm run bundle` again — the mounted profile loads `lib/`, so edits do not take effect until the bundle is rebuilt.
239
239
 
240
- `pnpm test` builds, typechecks and runs sixteen checks, none of which needs a dependency, a transformer or a browser. Each can also be run on its own with `pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:transition` / `pnpm check:repaint` / `pnpm check:code-plate` / `pnpm check:image-mode` / `pnpm check:rules` / `pnpm check:preset` / `pnpm check:ui` / `pnpm check:watchdogs` / `pnpm check:scheme` / `pnpm check:store` / `pnpm check:agent` / `pnpm check:rev` / `pnpm check:meta` / `pnpm check:node`:
240
+ `pnpm test` builds, typechecks and runs seventeen checks, none of which needs a dependency, a transformer or a browser. Each can also be run on its own with `pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:transition` / `pnpm check:repaint` / `pnpm check:code-plate` / `pnpm check:image-mode` / `pnpm check:rules` / `pnpm check:preset` / `pnpm check:ui` / `pnpm check:watchdogs` / `pnpm check:scheme` / `pnpm check:store` / `pnpm check:agent` / `pnpm check:rev` / `pnpm check:meta` / `pnpm check:mark` / `pnpm check:node`:
241
241
 
242
242
  - **`check:holiday`** — the two holiday windows, the Beijing day boundary, leap eighth months across 1900–2100, and the four gates `pickHoliday` applies.
243
243
  - **`check:rotation`** — `nextIndex` / `isRotating`: no walking off the end, shuffle never repeating the image on screen, and nothing called "rotating" with nothing to rotate.
@@ -254,6 +254,7 @@ After changing anything under `src/`, run `pnpm run bundle` again — the mounte
254
254
  - **`check:agent`** — agent access and the config revision: off by default, only an explicit `true` granting it (`'yes'` / `1` / `null` do not), the recommended profile not turning it on, an export not carrying it, the revision only ever moving forward, and a config written before either field reading as revision 0.
255
255
  - **`check:rev`** — the panel's revision chain: a write carries `expectedRev`, a successful one records the host's answer, `setRev` mutates the live object, an import neither zeroes the revision nor reads it after the wipe, installing the recommended profile goes through one `adoptRecommended` call rather than a `resetStore` plus a write, config writes are chained while picture bytes are released only after the config lands (imports included), a tool-added picture derives a slot name from the clock, and two callers cannot adopt at once. Miss any link and the next save refuses itself, so an edit visibly undoes itself ("click + once, the card flashes and disappears").
256
256
  - **`check:meta`** — how the plugin describes ITSELF in the plugin manager and the settings pages: `locale/<language>.json` carrying a title and a description per language, both files EXPORTED (the reader resolves them through Node's ESM resolver, so a file on disk that `exports` does not name resolves to nothing and is silently treated as absent), both listed in `files` (or the published tarball is missing them and looks identical), and an icon that is a relative path, inside the package, SVG/PNG/JPEG/WebP under 256 KiB, with a 36×36 viewBox and nothing referenced externally. When the harness is installed next door it ends by asking `readPluginMeta` itself, because the fallback it otherwise answers with — the bare package name — is exactly what a reader sees on the card.
257
+ - **`check:mark`** — how the plugin's own mark reaches the settings navigation: a `settings.section.icon` registration rather than a DOM patch over the host's markup, its id read from the same constant the section registers under (`only: row.id` silently drops any other id), no `MutationObserver` or `<svg>` surgery coming back, a glyph on the 16-unit grid drawn in `currentColor` (so one drawing suits the sidebar's text tint and the brand tile's foreground), and the published artwork's scale staying under the factor at which the viewBox starts clipping it. The DOM patch this replaced never matched, so the gear stayed and nothing reported anything.
257
258
  - **`check:node`** — the built node half through its real RPC handler with `DSH_HOME` redirected and `fetch` stubbed: the holiday block, the festival art's content-hash cache and its pruning, fallback across the three edges, the per-image color AND effect lifts, the global switch effect, the atomic config write with its backup and its recovery, the store's usage report and cleanup, and the recommended profile's seven pictures end to end (including the old manifest method being gone, the slot whitelist, and the retry budget).
258
259
 
259
260
  ## FAQ
@@ -310,13 +311,16 @@ The effect is **Instant**, or the duration is `0` — an effect and a duration a
310
311
 
311
312
  > Only the two most recent releases are listed here; older ones live in [CHANGELOG.md](./CHANGELOG.md).
312
313
 
313
- ### v0.12.2
314
+ ### v0.12.4
314
315
 
315
- - **The plugin manager and the settings pages describe this plugin properly** — `locale/en.json` and `locale/zh.json` carry its title and description in each language, and `icon.svg` (36×36, the same viewBox the built-in artwork uses) replaces the generic package glyph. Both are listed in `exports` and in `files`: the reader resolves them through Node's ESM resolver, so a locale file on disk that `exports` does not name resolves to nothing, and a file outside `files` is missing from the published tarball — both look exactly like "no locale at all" in the UI, with no error anywhere. `check:meta` pins the lot and ends by asking `readPluginMeta` itself what the card would show. The publish gate now accepts globs in `files`, so a `locale/*.json` entry no longer reads as a missing literal filename.
316
+ - **The nav mark redrawn** — a gentler slope, and the sun moved to hang just left of the first peak, because at 16px the steep hills read as a person and a sun over the summit made a symmetric emblem instead of the landscape the store icon shows.
317
+ - **Drawn at 20px in the brand tile, not 17** — inside a 30px tile the old size left the ink at 35% of the height, a ring of empty colour that reads as a mistake.
316
318
 
317
- ### v0.12.1
319
+ ### v0.12.3
318
320
 
319
- - **Fix: a picture the agent added had no theme colour** — the automatic extraction was hooked to the panel's own upload paths, and the agent's `add_image` reaches this half by none of them: the node half downloads and writes the config, and the browser half only adopts a newer revision. Both adoption sites now theme the slots that were not there before, so a picture added by the agent is themed like one added by hand, and the tools' own descriptions are finally true.
321
+ - **The settings nav draws this plugin's own mark** — it was a DOM patch looking for the nav button whose first `<span>` read as the section label, and the host puts the icon span first, so the text was empty and the patch silently found nothing; what showed was the harness's default gear. It is a `settings.section.icon` registration now, sharing one id constant with the section so the two cannot drift.
322
+ - **A new mark, drawn for 16px** — a sun over hills, the small counterpart of the store icon, on the 16-unit grid rather than scaled down from 36 (at nav size the larger drawing collapses). `currentColor`, so the sidebar tints it like any other row. The plain sun stays for the "follow the system theme" button.
323
+ - **The published icon is 20% larger** — its ink sat at 70%×78% of the 36 viewBox and read 37% of the sidebar container's width against 51% for a neighbouring plugin; 1.20 puts it at the same weight and still inside the viewBox. `check:mark` pins the registration, the shared id, the absent patch and that scale.
320
324
 
321
325
  ## Star History
322
326
  [![Star History Chart](https://api.star-history.com/chart?repos=HarmlessFunny/dsh-background-by-model&type=timeline&legend=bottom-right)](https://www.star-history.com/?repos=HarmlessFunny%2Fdsh-background-by-model&type=timeline&legend=bottom-right)
package/README.zh.md CHANGED
@@ -237,7 +237,7 @@ pnpm test # 构建 lib/、类型检查、跑十三道检查
237
237
 
238
238
  改完 `src/` 必须重新执行 `pnpm run bundle` 才会生效 —— 挂载的 profile 加载的是 `lib/`。
239
239
 
240
- `pnpm test` 会构建、类型检查并跑十六道校验,十六道都不需要依赖、不需要转译器、也不需要浏览器。也可以单独跑:`pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:transition` / `pnpm check:repaint` / `pnpm check:code-plate` / `pnpm check:image-mode` / `pnpm check:rules` / `pnpm check:preset` / `pnpm check:ui` / `pnpm check:watchdogs` / `pnpm check:scheme` / `pnpm check:store` / `pnpm check:agent` / `pnpm check:rev` / `pnpm check:meta` / `pnpm check:node`:
240
+ `pnpm test` 会构建、类型检查并跑十七道校验,十七道都不需要依赖、不需要转译器、也不需要浏览器。也可以单独跑:`pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:transition` / `pnpm check:repaint` / `pnpm check:code-plate` / `pnpm check:image-mode` / `pnpm check:rules` / `pnpm check:preset` / `pnpm check:ui` / `pnpm check:watchdogs` / `pnpm check:scheme` / `pnpm check:store` / `pnpm check:agent` / `pnpm check:rev` / `pnpm check:meta` / `pnpm check:mark` / `pnpm check:node`:
241
241
 
242
242
  - **`check:holiday`** —— 两个节日窗口、北京时间的跨日边界、1900–2100 年间的闰八月,以及 `pickHoliday` 的四道闸门。
243
243
  - **`check:rotation`** —— `nextIndex` / `isRotating`:不走越界、随机不重复当前这张、没有可轮换的东西时绝不报「正在轮换」。
@@ -254,6 +254,7 @@ pnpm test # 构建 lib/、类型检查、跑十三道检查
254
254
  - **`check:agent`** —— AI 授权与配置版本号:默认关闭、只有显式 `true` 才授予(`'yes'` / `1` / `null` 都不行)、推荐配置不替你打开、导出不带授权、版本号只增不减、以及旧配置读成 0。
255
255
  - **`check:rev`** —— 面板的版本号链:写入必须带 `expectedRev`、成功后必须记下宿主的回答、`setRev` 改写活对象、导入不得把版本号清零且要在清空之前读,推荐配置必须走一次 `adoptRecommended` 而不是自己 `resetStore` 再写,且配置写入必须串成一条链、图片字节要等配置落盘之后再删(导入主题也一样)、AI 加的图片用时钟派生的槽位名、两条路径不能同时采用推荐配置。少了任何一环,下一次保存就会判自己陈旧,改动在画面上自己撤销("点一次 + 卡片闪一下后消失")。
256
256
  - **`check:meta`** —— 插件在插件管理器和设置页里如何描述**它自己**:`locale/<语言>.json` 每种语言各带标题与描述、两份文件都在 `exports` 里(读取方走 Node 的 ESM 解析器,磁盘上有但 `exports` 没写等于解析不到,而且会被**静默当成不存在**)、都在 `files` 里(否则发布的包里根本没有,症状和上一句一模一样),以及一个相对路径、留在包内、SVG/PNG/JPEG/WebP、256 KiB 以内、带 36×36 viewBox 且不引用外部资源的图标。dsh 就装在旁边时,最后会直接问 `readPluginMeta` 本人——因为它给出的兜底答案正是包名,而那恰是卡片上原来显示的东西。
257
+ - **`check:mark`** —— 插件自己的标记如何抵达设置导航:用 `settings.section.icon` 插槽注册,而不是在宿主 DOM 上打补丁;它的 id 与分组注册共用同一个常量(`only: row.id` 会静默丢掉任何别的 id);`MutationObserver` 和替换 `<svg>` 那些做法不得回归;字形画在 16 单位网格上、整体 `currentColor`(所以一份画法既适配侧栏的文字色,也适配品牌方块的前景色);以及发布图标的缩放不得超过视框开始裁切它的那个系数。它替换掉的那个 DOM 补丁从来没匹配上过,所以齿轮一直在,而且什么都不报错。
257
258
  - **`check:node`** —— 把构建好的 node 半边通过真实的 RPC 处理器跑一遍(`DSH_HOME` 指向临时目录、`fetch` 换成桩):节日配置块、节日画按内容哈希的缓存与清理、三个边缘的回落、逐图颜色与逐图效果的抬升、全局切换效果、配置的原子写与备份/恢复、图片存储的用量报告与清理,以及推荐配置的七张图(含旧清单方法的消失、槽位白名单、超时与重试预算)。
258
259
 
259
260
  ## 常见问题
@@ -310,13 +311,16 @@ pnpm test # 构建 lib/、类型检查、跑十三道检查
310
311
 
311
312
  > 这里只列最近两个版本;更早的版本见 [CHANGELOG.zh.md](./CHANGELOG.zh.md)。
312
313
 
313
- ### v0.12.2
314
+ ### v0.12.4
314
315
 
315
- - **插件管理器和设置页里能正确描述这个插件了** —— `locale/en.json` 与 `locale/zh.json` 分语言给出标题与描述,`icon.svg`(36×36,和内置图标同一个 viewBox)顶掉那个通用方块。两样都写进了 `exports` 和 `files`:读取方走 Node 的 ESM 解析器,磁盘上有但 `exports` 没声明的 locale 文件解析不到,没写进 `files` 的文件发布的包里根本没有——这两种情况在界面上都长得像"根本没有 locale",且不报任何错。`check:meta` 把这些全钉住,最后直接问 `readPluginMeta` 卡片上会显示什么。发布闸门也改成支持 `files` 里的通配符了,`locale/*.json` 不会再被当成一个不存在的字面文件名。
316
+ - **导航标记重画** —— 坡更缓,太阳挪到第一个峰的左边:16px 下原来的陡坡看着像个人,太阳压在峰顶又让字形成了对称徽标,而不是商店图标里那座山。
317
+ - **品牌方块里按 20px 画,不再是 17** —— 在 30px 方块里,原来墨迹只占高度 35%,四周那一圈空看着像出了错。
316
318
 
317
- ### v0.12.1
319
+ ### v0.12.3
318
320
 
319
- - **修掉"AI 加的图片没有主题色"** —— 自动取色原来只挂在面板自己的上传路径上,而 AI 的 `add_image` 一条都不走:node 半边下载并写配置,浏览器半边只能接盘一个新版本号。现在两处接盘都会给"接盘前不存在的槽位"取色,AI 加的图和手动加的一样有主题色,工具描述里那句话才算数。
321
+ - **侧栏现在画的是插件自己的标记** —— 原来是一个 DOM 补丁,去找"第一个 `<span>` 文字等于分组名"的导航按钮,而宿主把图标那个 span 放在最前面,文字为空,补丁什么也没找到就静默返回,显示出来的其实是宿主的默认齿轮。现在改成注册 `settings.section.icon`,并与分组共用同一个 id 常量,两边不会漂移。
322
+ - **新标记,按 16px 画的** —— 太阳与山,商店图标的小尺寸对应画法,直接画在 16 单位网格上而不是把 36 的缩小(导航那个尺寸下大图会糊成一团)。整体 `currentColor`,侧栏按文字色着色。原来的纯太阳留给「跟随系统主题」按钮。
323
+ - **发布的图标放大了 20%** —— 墨迹原来只占 36 视框的 70%×78%,在侧栏里只占容器宽度 37%,而旁边的插件是 51%;1.20 让它回到同样分量,且仍在视框内。`check:mark` 钉住注册、共用 id、补丁不得回归,以及缩放上限。
320
324
 
321
325
  ## Star History
322
326
 
package/icon.svg CHANGED
@@ -1,15 +1,21 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" fill="none">
2
- <defs>
2
+ <!-- Drawn 1.2x about the artwork's own centre, so the mark fills the container
3
+ the way the other plugin icons do. The bbox before scaling is
4
+ x 5.50..30.70, y 3.75..31.95; 1.277 is the largest factor the viewBox
5
+ allows vertically, so this keeps headroom instead of touching the edge. -->
6
+ <g transform="translate(-3.720 -3.420) scale(1.2)">
7
+ <defs>
3
8
  <linearGradient id="j1" x1="8" y1="12" x2="28.5" y2="32" gradientUnits="userSpaceOnUse">
4
- <stop stop-color="#69B9FF"/><stop offset="1" stop-color="#324DE2"/>
9
+ <stop stop-color="#69B9FF"/><stop offset="1" stop-color="#324DE2"/>
5
10
  </linearGradient>
6
11
  <clipPath id="jclip"><rect x="8.5" y="12" width="20" height="20" rx="5"/></clipPath>
7
- </defs>
8
- <rect x="11" y="4.5" width="19" height="19" rx="4.5" transform="rotate(9 20.5 14)" fill="#4561EE" opacity=".3"/>
9
- <rect x="6" y="9" width="19" height="19" rx="4.5" transform="rotate(-6 15.5 18.5)" fill="#4561EE" opacity=".55"/>
10
- <rect x="8.5" y="12" width="20" height="20" rx="5" fill="url(#j1)"/>
11
- <g clip-path="url(#jclip)">
12
+ </defs>
13
+ <rect x="11" y="4.5" width="19" height="19" rx="4.5" transform="rotate(9 20.5 14)" fill="#4561EE" opacity=".3"/>
14
+ <rect x="6" y="9" width="19" height="19" rx="4.5" transform="rotate(-6 15.5 18.5)" fill="#4561EE" opacity=".55"/>
15
+ <rect x="8.5" y="12" width="20" height="20" rx="5" fill="url(#j1)"/>
16
+ <g clip-path="url(#jclip)">
12
17
  <circle cx="15.4" cy="17.8" r="2.1" fill="#fff" opacity=".95"/>
13
18
  <path d="M11.6 27.6l4.7-4.7 3.1 3.3 2.1-1.9 4.3 4.2" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" opacity=".92"/>
19
+ </g>
14
20
  </g>
15
21
  </svg>
package/lib/client.js CHANGED
@@ -4,8 +4,8 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react_jsx_runtime = require("react/jsx-runtime");
8
7
  let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let react_dom = require("react-dom");
10
10
  //#region src/client/runtime.ts
11
11
  function resolveStore() {
@@ -4566,6 +4566,48 @@ window.__ModuleLoader__.load({
4566
4566
  dangerouslySetInnerHTML: { __html: SUN_PATHS }
4567
4567
  });
4568
4568
  }
4569
+ /**
4570
+ * The plugin's own mark: a sun over hills, the 16px counterpart of `icon.svg`.
4571
+ *
4572
+ * NOT `SunIcon`, which stays the plain sun because its other caller
4573
+ * (`ModelBgPage`'s "follow the system theme" button) means a light/dark switch.
4574
+ * This one identifies the PLUGIN, so it carries the picture motif of the store
4575
+ * artwork: the rounded card becomes the sun, the mountain range stays.
4576
+ *
4577
+ * Drawn on the 16px grid rather than scaled down from the 36px icon — at this
4578
+ * size a 36px drawing's rounding and its two echo cards collapse into mush.
4579
+ *
4580
+ * FILLS THE GRID, which is a sizing decision rather than an aesthetic one. The
4581
+ * nav row hands this glyph 16px, and the host's own nav icons are drawn edge to
4582
+ * edge; an earlier version kept a couple of units of air around a smaller
4583
+ * drawing and read as a speck beside its neighbours. The ink now spans ~15.5 x
4584
+ * 13.1 of the 16 units.
4585
+ *
4586
+ * The slope is deliberately GENTLE and the sun sits clear of the summit: steep
4587
+ * hills plus a sun resting on the ridge line read as a person (or an M), not as
4588
+ * a landscape, at 16px.
4589
+ *
4590
+ * The sun's centre is at x 4.4, just LEFT of the first apex (x 5.7) — the
4591
+ * composition `icon.svg` has, where the sun hangs over the near slope rather
4592
+ * than over the peak. Centring it above the range (x 7.6) made the glyph read as
4593
+ * a symmetric emblem and lost that.
4594
+ *
4595
+ * `currentColor` throughout: the sidebar tints it with the text colour, the
4596
+ * brand tile behind the section header tints it with its own foreground.
4597
+ */
4598
+ const MARK_PATHS = "<circle cx=\"4.4\" cy=\"3.5\" r=\"1.95\" fill=\"currentColor\"/><path d=\"M1.2 13.7l4.5-5.3 2.85 3.4 2.15-2.5 4.1 4.4\" stroke=\"currentColor\" stroke-width=\"1.95\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>";
4599
+ function MarkIcon({ size = 16, className }) {
4600
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4601
+ width: size,
4602
+ height: size,
4603
+ className,
4604
+ viewBox: "0 0 16 16",
4605
+ fill: "none",
4606
+ xmlns: "http://www.w3.org/2000/svg",
4607
+ "aria-hidden": "true",
4608
+ dangerouslySetInnerHTML: { __html: MARK_PATHS }
4609
+ });
4610
+ }
4569
4611
  function Glyph({ children, size = 16, className }) {
4570
4612
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4571
4613
  width: size,
@@ -4820,63 +4862,39 @@ window.__ModuleLoader__.load({
4820
4862
  /**
4821
4863
  * The settings-nav glyph.
4822
4864
  *
4823
- * The harness derives every section's nav icon from its id and exposes no hook for
4824
- * a plugin to supply one, so the mounted nav cell has to be found in the DOM and
4825
- * its `<svg>` swapped for this plugin's sun. That is a patch on the host's markup,
4826
- * and the two failure modes are both silent: patching too early finds no cell, and
4827
- * patching on every mutation burns a scan of the whole document per keystroke in
4828
- * the composer. Hence the narrow trigger below — only a settings DIALOG arriving
4829
- * in the tree starts a scan.
4865
+ * The harness renders each settings section's nav icon from a slot,
4866
+ * `settings.section.icon`, whose registration id must EQUAL the section's id
4867
+ * (`dsh-client-ui-settings-general`, `renderSlot('settings.section.icon', …,
4868
+ * { only: row.id, fallback: navIcon(row.id) })`). The fallback is
4869
+ * `IconSettingsOutlineMedium` — a gear — which is why a section with no icon
4870
+ * registered shows a gear.
4871
+ *
4872
+ * THIS USED TO BE A DOM PATCH, and it never worked. It looked for the nav
4873
+ * button whose first `<span>` read as the section label, but the host's markup is
4874
+ * `<button><span class="navIcon"><svg/></span><span class="navLabel">…</span>`,
4875
+ * so `querySelector('span')` returned the ICON span — text content `''` — the
4876
+ * label never matched, and the gear stayed. Nothing reported it: the patch simply
4877
+ * found no cell and returned. A slot is both the supported route and the honest
4878
+ * one, so the observer, the markup scan and the DOM surgery are all gone.
4830
4879
  */
4831
- /** The host's settings dialog, the only place this section's nav exists. */
4832
- const DIALOG_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
4833
- const MARK = "dabNavIcon";
4880
+ /** The id the section is registered under; the icon's id has to match it. */
4881
+ const SECTION_ID = "dsh-background-by-model";
4834
4882
  /**
4835
- * Replace the nav glyph of the section whose label is `navLabel()`.
4883
+ * Register the section's nav icon.
4836
4884
  *
4837
- * Reads the label on every scan rather than capturing it: the section is rebuilt
4838
- * when the language changes, and a stale label would simply stop matching.
4885
+ * `active` arrives from the host on every render, so the mark can react to the
4886
+ * section being selected without re-registering.
4839
4887
  */
4840
- function applyNavIcon(navLabel) {
4841
- const nav = document.querySelector(DIALOG_SEL)?.querySelector("nav");
4842
- if (!nav) return;
4843
- for (const cell of Array.from(nav.querySelectorAll("button"))) {
4844
- const label = cell.querySelector("span");
4845
- if (label === null || label.textContent?.trim() !== navLabel) continue;
4846
- const svg = cell.querySelector("svg");
4847
- if (svg !== null && svg.dataset[MARK] === "1") return;
4848
- const sun = document.createElementNS("http://www.w3.org/2000/svg", "svg");
4849
- sun.setAttribute("width", "16");
4850
- sun.setAttribute("height", "16");
4851
- sun.setAttribute("viewBox", "0 0 16 16");
4852
- sun.setAttribute("fill", "none");
4853
- sun.setAttribute("xmlns", "http://www.w3.org/2000/svg");
4854
- sun.dataset[MARK] = "1";
4855
- sun.innerHTML = SUN_PATHS;
4856
- svg?.replaceWith(sun);
4857
- if (svg === null) cell.prepend(sun);
4858
- return;
4859
- }
4860
- }
4861
- /** Watch for the settings dialog (re)appearing; returns the disposer. */
4862
- function watchNavIcon(navLabel) {
4863
- if (typeof MutationObserver === "undefined") return () => void 0;
4864
- const observer = new MutationObserver((records) => {
4865
- if (records.some((r) => {
4866
- for (const n of r.addedNodes) {
4867
- if (n.nodeType !== 1) continue;
4868
- const el = n;
4869
- if (el.matches?.(DIALOG_SEL) || el.querySelector?.(DIALOG_SEL)) return true;
4870
- }
4871
- return false;
4872
- })) applyNavIcon(navLabel());
4873
- });
4874
- observer.observe(document.body, {
4875
- childList: true,
4876
- subtree: true
4877
- });
4878
- applyNavIcon(navLabel());
4879
- return () => observer.disconnect();
4888
+ function registerNavIcon(ctx) {
4889
+ ctx.slots.inject("settings.section.icon", () => ctx.slots.register({
4890
+ name: "settings.section.icon",
4891
+ id: SECTION_ID,
4892
+ order: 35,
4893
+ label: () => ""
4894
+ }, (props) => (0, react.createElement)(MarkIcon, {
4895
+ size: props.size ?? 16,
4896
+ className: props.active ? "dab-nav-mark is-active" : "dab-nav-mark"
4897
+ })));
4880
4898
  }
4881
4899
  //#endregion
4882
4900
  //#region src/client/theme-file.ts
@@ -8706,7 +8724,7 @@ window.__ModuleLoader__.load({
8706
8724
  title: t("repoLink"),
8707
8725
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8708
8726
  className: "dab-brand-tile",
8709
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 15 })
8727
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkIcon, { size: 20 })
8710
8728
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8711
8729
  className: "dab-brand-name",
8712
8730
  children: t("nav")
@@ -9583,14 +9601,14 @@ window.__ModuleLoader__.load({
9583
9601
  };
9584
9602
  ctx.slots.inject("settings.section", () => ctx.slots.register({
9585
9603
  name: "settings.section",
9586
- id: "dsh-background-by-model",
9604
+ id: SECTION_ID,
9587
9605
  order: 35,
9588
9606
  label: () => ctx.locale.bind(NS)("nav"),
9589
9607
  locale: NS,
9590
9608
  store,
9591
9609
  inject: sectionInject
9592
9610
  }, ThemeSection));
9593
- ctx.effect(() => watchNavIcon(() => ctx.locale.bind(NS)("nav")), "dsh-background-by-model: nav icon watch");
9611
+ registerNavIcon(ctx);
9594
9612
  const disposeThemeResets = watchThemeResets();
9595
9613
  ctx.effect(() => () => {
9596
9614
  disposeThemeResets();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-background-by-model",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpapers — every image with its own framing and theme color — plus layout mode, opacity, brightness and blur; the background follows the model you switch to, a rule can rotate through several images on its own rhythm, and an off-by-default switch lets the agent change any of it through two tools.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -71,9 +71,10 @@
71
71
  "check:scheme": "node scripts/scheme-check.ts",
72
72
  "check:store": "node scripts/store-check.ts",
73
73
  "check:node": "node scripts/node-half-check.mjs",
74
- "test": "tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/transition-check.ts && node scripts/repaint-check.ts && node scripts/code-plate-check.ts && node scripts/image-mode-check.ts && node scripts/rules-view-check.ts && node scripts/preset-check.ts && node scripts/ui-strings-check.ts && node scripts/watchdogs-check.ts && node scripts/scheme-check.ts && node scripts/store-check.ts && node scripts/agent-access-check.ts && node scripts/rev-check.ts && node scripts/plugin-meta-check.ts && node scripts/node-half-check.mjs",
74
+ "test": "tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/transition-check.ts && node scripts/repaint-check.ts && node scripts/code-plate-check.ts && node scripts/image-mode-check.ts && node scripts/rules-view-check.ts && node scripts/preset-check.ts && node scripts/ui-strings-check.ts && node scripts/watchdogs-check.ts && node scripts/scheme-check.ts && node scripts/store-check.ts && node scripts/agent-access-check.ts && node scripts/rev-check.ts && node scripts/plugin-meta-check.ts && node scripts/section-mark-check.ts && node scripts/node-half-check.mjs",
75
75
  "check:agent": "node scripts/agent-access-check.ts",
76
76
  "check:meta": "node scripts/plugin-meta-check.ts",
77
+ "check:mark": "node scripts/section-mark-check.ts",
77
78
  "check:rev": "node scripts/rev-check.ts"
78
79
  },
79
80
  "devDependencies": {