dsh-background-by-model 0.9.2 → 0.11.0

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,35 @@
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.9.2 / v0.9.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.11.0 / v0.10.0).
4
+
5
+ ## v0.11.0
6
+
7
+ - **The runtime download source moves to Bilibili's image CDN** — the recommended profile's seven wallpapers and both holiday pictures now come from `i0`/`i1`/`i2.hdslb.com`, three edges falling back to each other (measured: same path, same bytes, identical sha1). The README's three screenshots play no part at runtime and stay where they were.
8
+ - **The recommended profile's rules ship inside the plugin** — seven model rules and their interface numbers moved into `src/profile.ts`, and the button downloads only the seven pictures. It no longer depends on GitHub being reachable, and the `preset.json` manifest, the schema-version gate and the "not a profile" error codes retire with it: what gets installed is always what this build itself writes, and nothing remote chooses what that is.
9
+ - **The request set is a compile-time constant** — slot → BFS path, written out in one table, and `fetchPresetImage` honours only that table: a slot it does not carry answers `bad slot` before any request, so a wrong press cannot become "fetch 64 files because somebody else's document said so". `PRESET_IMAGE_COUNT_MAX` and that whole attack surface go with it.
10
+ - **A BFS path is a content hash, so an address is immutable** — the "a tag can be re-pointed at different bytes" case cannot arise, which also retires "remember to bump `*_ASSETS_REF` along with the art".
11
+ - **The addresses must carry `@3840w.webp`** — the uploads are 2.4–12 MB PNGs (76 MB for the set); that suffix is what serves 250–365 KB 4K WebPs, byte-for-byte what an existing data directory already holds, so nothing is re-downloaded and nothing loses detail on a 4K screen.
12
+ - **The festival cache is keyed by content hash** — `holiday-cache/<tag>/` became `holiday-cache/<first eight of the hash>/`, computed by `holidayArtGeneration` from the URL. New art means a new address, therefore a new directory and a guaranteed re-download, with no version constant to remember; the old tag directories (and 0.7.9's flat layout) are taken by the same `pruneHolidayCache` once the new art lands, whose keep-set is the deduplicated set of "the current generation of each holiday".
13
+ - **`SCHEMA_VERSION` stays 10** — nothing entered or left the config shape: what changed is where these numbers come from, not what they look like, so an older host and an older exported file both still read it.
14
+ - **`check:preset` now pins the built-in profile itself** — rule order and paintability, the slot ↔ URL table agreeing in both directions, the webp transform and content hash in every address, `profileImagePath`'s null whitelist, generations that cannot collide, and the old remote wiring being gone.
15
+ - **`check:node` rewrites the holiday and profile sections** — the three edges asked in order and always for the SAME path, a holiday failing only when every edge does, a retry budget of nine requests (three edges × three rounds) for weather worth waiting out, the old `fetchPresetConfig` method provably gone, the slot whitelist (holiday slots, table-missing slots and malformed names all refused before anything is requested), a stale `holiday-cache/v0.1.0` directory being swept, and `resetStore` still leaving the festival art alone.
16
+ - **`check:scheme` section 3 no longer reaches the network** — it used to fetch the remote `preset.json` and compare schemas; that manifest is gone, so it now pins that the client asks for no head at all, that the slot list comes from the built-in table, and that the addresses carry the webp transform at Bilibili's hosts.
17
+
18
+ ## v0.10.0
19
+
20
+ - **The config is written atomically, with a `.bak` of the last good file** — every save writes a temp file and renames it over the live one, rotating the current **parseable** file into `theme-config.json.bak` first. It used to be one in-place `writeFile`: a crash, a pulled cable or a full disk could leave half a JSON document, which `readConfig` answered with the factory defaults — and the next slider drag then persisted those defaults over the ruin. The original rules were gone for good.
21
+ - **A file that will not parse is restored from the backup, not treated as a fresh install** — when the live file is missing or corrupt and the `.bak` parses, that copy is read, written back to the live path, and reported once in the host log. Only when neither parses does the factory default answer.
22
+ - **The cleanup card still refuses on a corrupt config with no backup** — with a parseable backup it prunes against the backup's rules, because that is exactly the config the user is about to get back; with neither, the button stays disabled, because nothing on disk can be told apart from an orphan.
23
+ - **`resetStore` removes the `.bak` too** — leaving it behind would have the next read restore it, silently undoing the replacement the recommended profile exists to perform.
24
+ - **Opacity, brightness and blur moved from each RULE to each IMAGE** — the same layer the layout mode, the framing and the theme colour already live on: a daylight photo and a night shot in one rotation should not share one exposure. A schema-9 config's three rule-level values are lifted onto every one of its images on read, so upgrading changes nothing on screen.
25
+ - **`SCHEMA_VERSION` is 10** — the fields moved, and a schema-9 sanitizer rebuilds every image entry from the keys it knows: it would drop all three AND keep reading the rule-level values that no longer exist, so a dimmed wallpaper would come back at 100% in silence.
26
+ - **A blurred wallpaper no longer paints a dark rim around the viewport** — `blur()` treats everything outside the layer as transparent, so the edge came out half opaque and the page behind it showed through as a border that thickened with the slider. Each layer's box is now grown 2σ past the viewport (clipped by the container) with its placement moved back by the same amount, so the framing does not move.
27
+ - **A switch between two different opacity, brightness or blur values now animates** — each wallpaper layer keeps the filter of the picture it is painting, and the wallpaper's own opacity rides the switch instead of snapping to the incoming value on its first frame. Both used to be read live from the active image, so the outgoing picture was already wearing the incoming one's exposure before the fade ran: the transition was there, it just had nothing left to animate.
28
+ - **A repaint in the middle of a fade no longer cuts it short** — the steady opacity is mirrored in the render layer rather than read back from the store, so a theme re-registration or a panel re-render re-writes the value the fade is heading for.
29
+ - **Dragging a wallpaper's opacity, brightness or blur no longer repaints everything on every frame** — a drag frame writes only the property that field owns on the wallpaper layer, and the full `setRule` (skin, palette, wallpaper) runs once when the gesture ends. It used to be one full repaint per frame, which a large image made visibly slow.
30
+ - **`setWpOpacity` / `setWpFilter` have callers again** — the two zero-lag writers had none since the merged slider landed, while a comment and `check:image-mode` still asserted "two sites". `check:image-mode` now pins that the panel reaches them, and that a preview only touches the rule that is on screen.
31
+ - **The slider's preview and its commit are separate now** — `onInput` previews each frame and the commit rides the native `change` event, once. React's `onChange` for a range input is wired to that same `input` event, so committing from it was committing per frame.
32
+ - **Checks** — `check:node` gains a section on the atomic write and the backup (a corrupt file must not overwrite the good copy, no temp file survives, a reset takes the backup with it, a corrupt file heals from the copy) and pins the per-image lift, its clamps, the absent key and the holiday entry; `check:image-mode` pins that the render layer reads the image being PAINTED, that the panel writes the image being EDITED, that `defaultImage` is the only creation path, that the blurred edge is sampled off screen (the 2σ growth, written on the drag frame too), and that a layer carries its own picture's three effects rather than a live read.
4
33
 
5
34
  ## v0.9.2
6
35
 
package/CHANGELOG.zh.md CHANGED
@@ -1,6 +1,35 @@
1
1
  # 更新日志
2
2
 
3
- > 这里保留完整的历史记录;[README.zh.md](./README.zh.md#近期优化) 的「近期优化」一节只列最近两个版本(v0.9.2 / v0.9.1)。
3
+ > 这里保留完整的历史记录;[README.zh.md](./README.zh.md#近期优化) 的「近期优化」一节只列最近两个版本(v0.11.0 / v0.10.0)。
4
+
5
+ ## v0.11.0
6
+
7
+ - **运行时下载源换成 B 站图床** —— 推荐配置的七张壁纸与中秋、国庆两张节日画全部改从 `i0/i1/i2.hdslb.com` 取,三个边缘互为回落(实测同样路径、同样字节,sha1 一致)。README 的三张截图不参与运行时,仍留在原来的地方。
8
+ - **推荐配置的规则改成随插件内置** —— 七条模型规则与那套界面数值搬进 `src/profile.ts`,按钮只下载七张图。于是它不再依赖 GitHub 能通,`preset.json` 清单、schema 版本闸与「不是配置」这类错误码一起退休:能被安装的规则永远就是这份构建自己写的那些,远端没有任何东西在选择装什么。
9
+ - **请求清单变成编译期常量** —— 槽位 → BFS 路径写死在一张表里,而 `fetchPresetImage` 只认这张表:表里没有的槽位直接回 `bad slot`,所以一次按错不可能变成「按别人给的清单去取 64 个文件」。`PRESET_IMAGE_COUNT_MAX` 与 65 张图那条攻击面随之消失。
10
+ - **BFS 路径是内容哈希,地址天然不可变** —— 不再有「tag 被重新指向别的字节」这种可能;顺带把「换素材要记得同时改 `*_ASSETS_REF`」这个坑一起消掉了。
11
+ - **地址必须带 `@3840w.webp`** —— 上传的原图是 2.4–12 MB 的 PNG(九张合计 76 MB),这样取才是 250–365 KB 的 4K WebP,并且与现有数据目录里那几份的 sha1 逐字节一致,所以 4K 屏上不掉画质、老用户也不需要重下。
12
+ - **节日画的缓存目录按内容哈希分代** —— `holiday-cache/<tag>/` 变成 `holiday-cache/<哈希前八位>/`,由 `holidayArtGeneration` 从 URL 现算。换图 = 换地址 = 换目录 = 必然重下,没有需要记得去改的版本常量;旧的 tag 目录(以及 0.7.9 的平铺布局)在新图落盘后由同一次 `pruneHolidayCache` 带走,清扫时的保留集是「每个节日当前那一代」的去重集合。
13
+ - **`SCHEMA_VERSION` 保持 10** —— 这次没有字段进出配置形状:变的是这些数值从哪来,不是它们长什么样,所以旧宿主、旧导出文件都照常读。
14
+ - **`check:preset` 改成钉内置配置本身** —— 规则顺序与可画性、槽位 ↔ 地址表的双向一致、每个地址的 webp 变换与内容哈希、`profileImagePath` 的 null 白名单、缓存分代不重复,以及旧的远端接线已经不在。
15
+ - **`check:node` 重写节日与推荐配置两节** —— 三个边缘的依次回落与「同一条路径」、全部边缘失败才失败、「一次坏天气值得再来一轮」的重试预算(3 边缘 × 3 轮 = 9 个请求封顶)、旧 `fetchPresetConfig` 方法确实已不存在、槽位白名单(节假日槽位、表外槽位、非法名都在下载前被拒)、`holiday-cache/v0.1.0` 这类旧目录会被清扫,以及 `resetStore` 仍然不动节日画。
16
+ - **`check:scheme` 第 3 节不再联网** —— 它原本要拉远端 `preset.json` 比 schema,现在清单不存在了,改为钉住「客户端不再请求清单」「槽位清单来自内置表」「地址带 webp 变换且指向 B 站」。
17
+
18
+ ## v0.10.0
19
+
20
+ - **配置改成原子写,并留一份 `.bak`** —— 每次保存先写临时文件、再改名覆盖,覆盖前把当前这份**能解析**的文件旋转成 `theme-config.json.bak`。此前是原地 `writeFile`:崩溃、断电或磁盘写满都可能留下半截 JSON,而 `readConfig` 遇到读不出来的文件会退回出厂默认,下一次拖动滑块就把这份默认写回去,原规则表彻底没了。
21
+ - **读不出来时从备份恢复,而不是当新装** —— 主文件缺失或损坏、而 `.bak` 能解析时,读它、把它写回主文件,并在宿主日志里说一句;两份都读不出时才是出厂默认。
22
+ - **清理卡在「没有备份的坏配置」上仍然拒绝删图** —— 有能解析的备份时,清理按备份里的规则走(那正是用户即将拿回的配置);一份都没有时按钮照旧是灰的,因为盘上没有任何东西能和孤儿区分开。
23
+ - **`resetStore` 连 `.bak` 一起删** —— 留着它,下一次读取会把它恢复回来,悄悄抵消掉「推荐配置」那次真正的替换。
24
+ - **透明度、亮度与模糊从「每条规则」下沉到「每张图」** —— 与布局模式、取景、主题色落在同一层:一组轮换里的一张白天照片和一张夜景,本来就不该共用一套曝光。schema-9 的规则级三个值在读取时一次性抬到它的每一张图上,所以升级不改变屏幕上任何东西。
25
+ - **`SCHEMA_VERSION` 升到 10** —— 字段搬了位置,而 schema-9 的 sanitizer 会按它知道的键重建每个图片条目:它既会丢掉这三个字段,也会继续读已经不存在的规则级值,于是一张被调暗的壁纸会静默回到 100%。
26
+ - **模糊的壁纸不再在视口四周画一圈黑边** —— `blur()` 把图层之外一律当作透明,于是边缘只有一半不透明,背后的页面透出来就是一道随模糊值变粗的暗边。现在每个图层的盒子向视口外各扩 2σ(由容器裁掉),取景位置同时补回同样的距离,画面不动。
27
+ - **透明度、亮度或模糊不同的两张图之间切换,现在真的会有动画** —— 每个壁纸图层记住的是**它正在画的那张图**的滤镜,壁纸自身的透明度则跟着过渡一起走,而不是在开始那一帧直接切成新值。此前两者都实时读当前图,等于 fade 还没跑,出去的图就已经换上新图的曝光了 —— 动画一直在,只是没有东西可动。
28
+ - **过渡中途重绘不会再把它掐断** —— 稳定后的透明度记在渲染层自己手里,而不是从 store 回读,所以主题重注册或面板重渲染时写回的是这次过渡要去的值,不是它离开的那个值。
29
+ - **拖动壁纸的透明度、亮度与模糊不再每帧整块重绘** —— 拖动只写壁纸层自己的那个属性,松手才走一次完整的 `setRule`(皮肤、调色板、壁纸一起)。此前拖动一帧就是一次全量重绘,图一大就明显卡。
30
+ - **滑块的预览与提交分开了** —— `onInput` 每帧只预览,提交改由原生 `change` 事件触发、一次落地;range 输入的 React `onChange` 本来就绑在同一个 `input` 事件上,所以按它提交等于按帧提交。
31
+ - **`setWpOpacity` / `setWpFilter` 又有人调用了** —— 这两个零延迟写入函数在合并滑块那一版之后一直没有调用者,而注释和 `check:image-mode` 仍按「两个站点」断言。`check:image-mode` 现在钉住面板真的接上了它们,以及预览只作用于正在生效的那条规则。
32
+ - **门禁** —— `check:node` 新增原子写与备份一节(坏文件不得覆盖好备份、临时文件不残留、清空连备份一起走、坏文件从备份恢复),并把「每张图三个效果」的抬升、上下限、缺省键与节日条目一起钉住;`check:image-mode` 钉住渲染层读的是**正在画的那张图**、面板写的是**正在编辑的那张图**、`defaultImage` 是所有创建点的唯一入口、模糊边缘取样到屏幕之外(2σ 扩幅,拖动那一帧也写),以及每个图层携带的是自己那张图的三个效果而不是实时读取。
4
33
 
5
34
  ## v0.9.2
6
35
 
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  > Forked from [`Tkingxiao/dsh-any-background`](https://github.com/Tkingxiao/dsh-any-background) and renamed to `dsh-background-by-model`.
11
11
 
12
- A **DeepSeek Harness** appearance plugin built around an **ordered list of model rules**: each rule carries its own images, opacity, brightness and blur, and each image its own layout mode, framing and theme color. Switch the model and the background switches with it, through the global [switch effect](#switch-effect-global) you choose on the Config page.
12
+ A **DeepSeek Harness** appearance plugin built around an **ordered list of model rules**: each rule carries its own images, and each image its own layout mode, framing, theme color, opacity, brightness and blur. Switch the model and the background switches with it, through the global [switch effect](#switch-effect-global) you choose on the Config page.
13
13
 
14
14
  > **v0.3.0 was a breaking release** — video wallpapers, generated backgrounds and the single global theme color were removed. See the [v0.3.0 entry in CHANGELOG.md](./CHANGELOG.md#v030).
15
15
 
@@ -61,7 +61,7 @@ One interface, one config — only the current model differs:
61
61
  <p align="center">
62
62
  <img src="https://cdn.jsdelivr.net/gh/HarmlessFunny/assets@v0.1.0/dsh-background-by-model/rule-editor.webp" alt="One rule's editor" width="660">
63
63
  <br/>
64
- <em>One rule on the Model Background tab · the wallpaper, its opacity, its brightness and its blur belong to the rule; the layout mode, the framing and the theme color belong to the image being edited</em>
64
+ <em>One rule on the Model Background tab · the layout mode, the framing, the theme color and the opacity/brightness/blur all belong to the image being edited</em>
65
65
  </p>
66
66
 
67
67
  <p align="center">
@@ -74,7 +74,7 @@ One interface, one config — only the current model differs:
74
74
 
75
75
  An ordered list of rules, matched first-hit against the current model's name, with rule 1 as the fallback.
76
76
 
77
- - **Rules** — add, remove, reorder and name them; each carries its own match string, opacity, brightness and blur.
77
+ - **Rules** — add, remove, reorder and name them; each carries its own match string, and a set of images with their own look.
78
78
  - **Images per rule** — a filmstrip filled by file picker, drag & drop or a list of URLs; reorder it, promote one to first, or replace one in place (which keeps its position in the rotation). A rule may also hold none, and an empty rule still matches while it has a theme color of its own, so emptying one is a visible state rather than a trap.
79
79
  - **Live match readout** — the Model Background tab shows which model was read and which rule it resolved to.
80
80
  - **Import / export** — the whole rule set as `dsh-background-by-model-theme.json` with every image inlined; files written by earlier releases still import.
@@ -107,7 +107,7 @@ One switch on the Config page and nothing else: on 中秋节 and 国庆节 the w
107
107
 
108
108
  - **中秋 is 农历八月十五**, resolved from the Chinese calendar in **Asia/Shanghai**, so it moves every year (2024-09-17, 2025-10-06, 2026-09-25 …); **国庆 is October 1–7**; when the two collide, 中秋 wins.
109
109
  - **A holiday with no image falls through** to the model rules rather than blanking the wallpaper.
110
- - **The art is downloaded, not shipped** — one compressed wallpaper per holiday, pinned to a release tag of the project's [assets repository](https://github.com/HarmlessFunny/assets) and cached under `holiday-cache/<tag>/` on the first read of the day. The download is gated on the calendar, so an ordinary day fetches nothing at all and every later boot is answered from disk.
110
+ - **The art is downloaded, not shipped** — one compressed wallpaper per holiday, served by **Bilibili's image CDN** (`i0`/`i1`/`i2.hdslb.com`, three edges falling back to each other) and cached under `holiday-cache/<hash>/` on the first read of the day. The directory is named after the **content hash** in the URL: new art means a new address, therefore a new directory and a guaranteed re-download — there is no version constant to remember to bump. The download is gated on the calendar, so an ordinary day fetches nothing at all and every later boot is answered from disk.
111
111
  - **Neither the art nor its palette is swappable** — a holiday slot is read-only, the colors and the full-bleed Fill mode come from the definition, and theme exports carry your rules only.
112
112
 
113
113
  ### Per-rule and per-image settings
@@ -116,7 +116,7 @@ One switch on the Config page and nothing else: on 中秋节 and 国庆节 the w
116
116
  - **Theme color** — HSL wheel, numeric input, **Extract from this image** and an eyedropper. It belongs to the **image**, and an image with none follows the system theme rather than inheriting its rule's.
117
117
  - **Layout mode** — fit / fill / stretch / tile / center, stored per image; disabled on a rule that holds no picture, rather than hidden.
118
118
  - **Framing** — drag to pan and scroll to zoom, editable in fit mode only, stored per image and stable across window resizes.
119
- - **Image opacity** `0–100%`, **image brightness** `0–200%` (`100%` is the picture as saved) and **image blur** `0–60 px`, all three per rule and edited through **one slider with a menu beside it** that picks the effect. They treat the image (wallpaper) layer only — the Interface tab's opacity treats interface surfaces.
119
+ - **Image opacity** `0–100%`, **image brightness** `0–200%` (`100%` is the picture as saved) and **image blur** `0–60 px`, all three **saved per image** and edited through **one slider with a menu beside it** that picks the effect — so a daylight photo and a night shot in one rotation keep their own exposures. They treat the image (wallpaper) layer only; the Interface tab's opacity treats interface surfaces. A rule with no image has all three disabled rather than hidden.
120
120
 
121
121
  ### Global settings (Interface tab)
122
122
 
@@ -126,12 +126,15 @@ One switch on the Config page and nothing else: on 中秋节 and 国庆节 the w
126
126
 
127
127
  ### Recommended profile
128
128
 
129
- One click on the Config tab downloads the author's own configuration — rules, wallpapers and interface settings — from the project's assets repository.
129
+ One click on the Config tab installs the author's own configuration — seven model rules and their interface numbers **ship inside the plugin**, and only the seven wallpapers are downloaded, from **Bilibili's image CDN**.
130
130
 
131
- - **Hosted in the store's own shape** (`preset/preset.json`, `preset/theme-config.json`, one `modelbg-<slot>` file per wallpaper), so it stays editable on GitHub and publishing it is a `cp`.
131
+ - **The rules are no longer a remote file** — they are compiled into the bundle, at the same version as the code that reads them. So this button does not need GitHub to be reachable, and there is no "the remote profile is newer than this build" case left to refuse: what gets installed is always what this build itself writes.
132
+ - **The wallpaper addresses are a constant table** — slot → BFS path, fixed at build time. The request set never comes off the network, so a wrong press cannot turn into "fetch 64 files because somebody else's document said so".
133
+ - **One address, one set of bytes** — a BFS path is a hash of the upload, so a wallpaper URL is immutable in a way a git tag is not.
134
+ - **`@3840w.webp` is load-bearing** — the uploads are 2.4–12 MB PNGs (76 MB for the set); with that suffix they are the 250–365 KB 4K WebPs an existing data directory already holds, byte for byte.
132
135
  - **Every interface number in it is what the plugin already ships with**, so on a new install the button adds rules and pictures; on a store you have already tuned it resets those too, because it is a whole configuration and not a patch.
133
136
  - **The download comes before the question**, so the confirmation can say what is actually in hand — and the wipe comes after the download, never before it. It is the only card in the panel that **asks before it deletes**.
134
- - **One missing wallpaper fails the whole attempt and names the slot**, and every file retries on its own, keeping what arrived for the next press.
137
+ - **One missing wallpaper fails the whole attempt and names the slot**, and every file retries on its own (three edges × three rounds), keeping what arrived for the next press.
135
138
 
136
139
  ### Image storage cleanup
137
140
 
@@ -141,6 +144,7 @@ The last card on the Config tab. Images are stored at **full fidelity**, so an i
141
144
  - **Only unreferenced files** — every byte a current rule uses is untouched, and the config itself is never written. The card re-reads after the sweep instead of subtracting, so a file that failed to delete still counts.
142
145
  - **Failures are named** — one undeletable file does not protect the other six, and it is not reported as success either: the toast lists which ones.
143
146
  - **No confirmation step** — what it deletes is by definition referenced by no rule, and is not a choice made from a working set, so a "confirm?" here only teaches people to click through. The safety is in the **gate**: when the config cannot be read the button is disabled (next bullet), rather than seven files being listed and then asked about.
147
+ - **A corrupt live file is answered by the backup** — when the live config will not parse but the `.bak` does, the sweep runs against the **backup's** rules, because that is the config you are about to get back; only when neither parses does it read as unknown and disable the button.
144
148
  - **It also clears a stray `modelbg-h-*`** — that slot belongs to no config (festival art lives in `holiday-cache/`, and the slot is read-only), so this is the only path that can ever remove such a file.
145
149
  - **An older host reads as "unknown", not "0 B"** — the two nothings must not look the same.
146
150
 
@@ -162,9 +166,10 @@ Data directory: `~/.dsh/.dsh-background-by-model-data/` (Windows: `C:\Users\<you
162
166
 
163
167
  | File | Contents |
164
168
  | --- | --- |
165
- | `theme-config.json` | The rule list (each rule with its image list and rotation) plus the global Interface settings, the global switch effect and the holiday overrides |
169
+ | `theme-config.json` | The rule list (each rule with its image list, and each image with its own look and rotation settings) plus the global Interface settings, the global switch effect and the holiday overrides |
170
+ | `theme-config.json.bak` | The previous config, as of the last **successful** save. Every save writes a temp file and renames it over the live one, rotating the current good file into `.bak` first; a live file that will not parse is restored from here instead of falling back to the factory defaults. With neither readable, the cleanup card refuses to delete images |
166
171
  | `modelbg-<slot>` | One image, stored as raw bytes without a file extension — one file per image, so a rule with three images owns three slots |
167
- | `holiday-cache/<tag>/<asset>` | Festival art downloaded from the CDN, one file per holiday (`mid-autumn.webp`, `national-day.webp`) inside a directory named after the assets release this build pins (`v0.1.0`). The tag is part of the path so that a build pinning a new tag starts from an empty directory instead of being answered with the previous generation's bytes; the file inside is named after the **asset**, deliberately outside the slot namespace, which is what keeps a holiday slot read-only. Superseded generations are pruned once the new art is on disk. Delete a file here and it is simply re-downloaded the next time that holiday is in force |
172
+ | `holiday-cache/<hash>/<asset>` | Festival art downloaded from Bilibili's image CDN, one file per holiday (`mid-autumn.webp`, `national-day.webp`) inside a directory named after the **first eight characters of that picture's content hash** (`42e22801`, `30e97211` today). The hash is part of the path so that new art lands in a directory nobody has written to — it cannot be answered with the previous generation's bytes, and there is no version constant to keep in step. The file inside is named after the **asset**, deliberately outside the slot namespace, which is what keeps a holiday slot read-only. Superseded generations (including every `holiday-cache/<tag>/` from 0.10 and earlier) are pruned once the new art is on disk. Delete a file here and it is simply re-downloaded the next time that holiday is in force |
168
173
 
169
174
  ## Installation
170
175
 
@@ -224,14 +229,14 @@ After changing anything under `src/`, run `pnpm run bundle` again — the mounte
224
229
  - **`check:transition`** — `resolveFadeMs` / `transitionPlan`: the shipped default still producing byte-for-byte the transition string earlier releases wrote, every route to a hard cut being one, reduced motion vetoing rather than shortening, and every effect ending visible and unscaled.
225
230
  - **`check:repaint`** — `shouldRepaint`, including the edit that **creates** the winner, plus a failure if any write path stops asking, or if a rotation step stops re-emitting the interface palette.
226
231
  - **`check:code-plate`** — the code block's plate: its tokens declared on both `:root` and `body`, the light branch emitting the banner, and each surface reading its own plugin variable.
227
- - **`check:image-mode`** — the layout mode's lift onto the image, and which image a control edits against which the render layer reads.
232
+ - **`check:image-mode`** — the lift onto the image for the layout mode and the three paint effects, and which image a control edits against which the render layer reads.
228
233
  - **`check:rules`** — the two decisions behind the tab strip, which live in `src/client/rules-view.ts`, plus the page structure the layout rests on.
229
- - **`check:preset`** — the recommended profile's retry policy and what a failed attempt holds for the next one, with both halves shown using them.
234
+ - **`check:preset`** — the profile itself (its rules, its slot → URL table and its generation constant) alongside the retry policy and what a failed attempt holds for the next one, with both halves shown using them. The rules are compiled in now, so this is also what catches a rule pointing at a slot the table does not carry — a mistake that would otherwise only show up after a release.
230
235
  - **`check:ui`** — both dictionaries carrying the same keys, every `dab-…` class styled, no dead entry, and every `var(--dsw-…)` published by the installed theme.
231
236
  - **`check:watchdogs`** — one heartbeat, entries firing on their own due time, paused while the tab is hidden, no timer left by dispose, and no module outside the entry starting its own interval.
232
- - **`check:scheme`** — the `data-ds-dark-theme` hand-off: the plugin only rewrites the value it wrote, hands the flag back when it drops its tokens, and repaints when the scheme moves.
237
+ - **`check:scheme`** — the `data-ds-dark-theme` hand-off: the plugin only rewrites the value it wrote, hands the flag back when it drops its tokens, and repaints when the scheme moves. It no longer reaches the network: the recommended profile's manifest it used to compare against is compiled in now.
233
238
  - **`check:store`** — the two decisions behind the storage cleanup, `referencedSlots` and `orphanSlots`: a picture in use is never an orphan, a stray holiday-slot file always is, and the browser half sends RULE slots rather than the whole config.
234
- - **`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 caching and pruning, mirror fallback, the per-image color lift, the global switch effect, the store's usage report and cleanup, and the recommended profile end to end.
239
+ - **`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).
235
240
 
236
241
  ## FAQ
237
242
 
@@ -242,7 +247,10 @@ Rule 1 probably has an empty match string, which makes it a pure fallback holdin
242
247
  Matching is first-hit, not best-hit: put the more specific rule higher in the list, or reorder.
243
248
 
244
249
  **Where are my wallpapers?**
245
- In `~/.dsh/.dsh-background-by-model-data/`, one `modelbg-<slot>` file per image plus `theme-config.json`.
250
+ In `~/.dsh/.dsh-background-by-model-data/`, one `modelbg-<slot>` file per image plus `theme-config.json` and its `.bak` backup.
251
+
252
+ **What is `theme-config.json.bak`, and can I delete it?**
253
+ The previous config as of the last **successful** save. Every save writes a temp file and renames it over the live one, keeping the then-current parseable file as the backup — so if the live file ever stops parsing (a crash, a pulled cable, a hand edit gone wrong), the plugin restores from it instead of falling back to the factory defaults. You can delete it; the cost is having nothing to restore the next time the live file really is broken.
246
254
 
247
255
  **I turned the rotation on and the background never changed.**
248
256
  Three things can hold it: the rule needs two or more images, the tab has to be visible, and only the rule the current model resolved to rotates.
@@ -284,21 +292,24 @@ The effect is **Instant**, or the duration is `0` — an effect and a duration a
284
292
 
285
293
  > Only the two most recent releases are listed here; older ones live in [CHANGELOG.md](./CHANGELOG.md).
286
294
 
287
- ### v0.9.2
288
-
289
- - **The Config tab gains an "Image storage" card** — it reports how much is cleanable and how many files that is, then offers the button; only unreferenced files go, the config and the festival art stay, and any file that resists is named.
290
- - **`check:store`, so there are thirteen checks now** — both READMEs had been claiming ten, having missed `check:watchdogs` and `check:scheme` since v0.8.1; they are listed again alongside the new one.
291
-
292
- ### v0.9.1
295
+ ### v0.11.0
293
296
 
294
- - **The three wallpaper sliders are now one slider plus a picker** — the options read Image opacity / Image brightness / Image blur, the picker matches the buttons, and the hint below is gone.
297
+ - **The download source moves to Bilibili's image CDN** — the recommended profile's seven wallpapers and both holiday pictures now come from `i0`/`i1`/`i2.hdslb.com`, three edges falling back to each other; the three screenshots in this README stay where they are, since they play no part at runtime.
298
+ - **The recommended profile's rules ship inside the plugin** — seven model rules and their interface numbers are compiled into the bundle, and the button downloads only the seven pictures. It no longer needs GitHub to be reachable, and there is no "the remote profile is newer than this build" gate left, nor a `preset.json` manifest — what gets installed is always what this build itself writes.
299
+ - **The request set is a constant table** — slot → BFS path, fixed at build time, and a BFS path is a hash of the upload. So a wallpaper URL is immutable in a way a git tag is not.
300
+ - **The addresses carry `@3840w.webp`** — the uploads are 2.4–12 MB PNGs (76 MB for the set); with that suffix they are the 250–365 KB 4K WebPs whose sha1 matches what is already in a data directory.
301
+ - **The festival cache is keyed by content hash** — `holiday-cache/<tag>/` became `holiday-cache/<first eight of the hash>/`. New art means a new address, therefore a new directory and a guaranteed re-download; the "remember to bump the version too" trap is gone, and the old tag directories are taken by the same sweep once the new art lands.
302
+ - **`SCHEMA_VERSION` stays 10** — nothing entered or left the config shape: what changed is where those numbers come from, not what they look like.
303
+ - **Checks** — `check:preset` now pins the built-in profile itself (rule order, the slot ↔ URL table, the webp transform and content hash in each address, the cache generation); `check:node` rewrites the holiday and profile sections (three-edge fallback and the retry budget, the old `fetchPresetConfig` method being gone, the slot whitelist, tag directories being swept); section 3 of `check:scheme` no longer reaches the network.
295
304
 
296
- ### v0.9.0
305
+ ### v0.10.0
297
306
 
298
- - **The RPC channel is the plugin's own prefix route on `webServer`**, so envelope, body cap and the auth fence stay here and `requestRejection` is asked per request.
299
- - **One scheduler for every recurring check** — model poll, theme watchdog, holiday rollover and the delayed restores share a clock, pause while the tab is hidden, and stop with one dispose.
300
- - **The browser entry point is wiring again** — skin, watchdogs, rotation, boot, model state, extraction and theme files are their own modules.
301
- - **Exporting a theme no longer cancels its own download**, and comments say why the code is what it is rather than what it used to be.
307
+ - **The config is written atomically, with a `.bak` of the last good file** — a save writes a temp file and renames it over the live one, keeping the previous parseable file as the backup; a corrupt live file is restored from it instead of falling back to the factory defaults and then being overwritten by your next drag.
308
+ - **Opacity, brightness and blur are saved per image** — the same layer as the layout mode, framing and theme color; a schema-9 config's rule-level values are lifted onto every image on read, so nothing changes on screen.
309
+ - **`SCHEMA_VERSION` is 10** — the fields moved, and an older host would drop them in silence, so the browser half has to know.
310
+ - **A blurred wallpaper no longer paints a dark rim around the viewport** — the layer's box is grown 2σ past the screen edge and clipped, with the framing moved back by the same amount, so the blur samples off screen instead of fading into the page behind it.
311
+ - **A switch between two different opacity, brightness or blur values animates now** — each layer keeps its own picture's filter, and the wallpaper's opacity rides the transition instead of snapping.
312
+ - **Dragging one of the three sliders no longer repaints everything per frame** — a drag frame writes only that field's property, and one full repaint lands when the gesture ends.
302
313
 
303
314
  ## Star History
304
315
  [![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
@@ -9,7 +9,7 @@
9
9
 
10
10
  > 派生自 [`Tkingxiao/dsh-any-background`](https://github.com/Tkingxiao/dsh-any-background),并重命名为 `dsh-background-by-model`。
11
11
 
12
- 一个 **DeepSeek Harness** 外观插件,核心是一份**有序的模型规则列表**:每条规则自带壁纸(可多张)、透明度、亮度与模糊度,**每张图各有自己的布局模式、取景与主题色**。切换模型即切换背景,切换过程走你在「配置」页选定的全局[切换效果](#切换效果全局)。
12
+ 一个 **DeepSeek Harness** 外观插件,核心是一份**有序的模型规则列表**:每条规则自带壁纸(可多张),**每张图各有自己的布局模式、取景、主题色,以及透明度、亮度与模糊度**。切换模型即切换背景,切换过程走你在「配置」页选定的全局[切换效果](#切换效果全局)。
13
13
 
14
14
  > **v0.3.0 是破坏性更新** —— 视频背景、生成式背景与全局主题色均已被移除。详见 [CHANGELOG.zh.md 的 v0.3.0 条目](./CHANGELOG.zh.md#v030)。
15
15
 
@@ -61,7 +61,7 @@
61
61
  <p align="center">
62
62
  <img src="https://cdn.jsdelivr.net/gh/HarmlessFunny/assets@v0.1.0/dsh-background-by-model/rule-editor.webp" alt="单条规则的编辑器" width="660">
63
63
  <br/>
64
- <em>「模型背景」页的单条规则 · 壁纸、透明度、亮度与模糊度属于这条规则;布局模式、取景与主题色属于它正在编辑的那张图</em>
64
+ <em>「模型背景」页的单条规则 · 布局模式、取景、主题色与透明度/亮度/模糊,全都属于它正在编辑的那张图</em>
65
65
  </p>
66
66
 
67
67
  <p align="center">
@@ -74,7 +74,7 @@
74
74
 
75
75
  一份有序的规则列表,按当前模型名先命中者胜,第 1 条兼任兜底。
76
76
 
77
- - **规则** —— 增删、排序、命名;每条自带匹配串、透明度、亮度与模糊度。
77
+ - **规则** —— 增删、排序、命名;每条自带一个匹配串,以及一组图片与它们各自的外观。
78
78
  - **每条规则多张图** —— 胶片条可用文件选择、拖放或一行一个网址填入;可排序、设为第一张、或原地替换(替换会保住它在轮换里的位置)。规则也可以一张图都没有,而只要它还有自己的主题色,空规则依旧参与匹配,所以「清空」是一个看得见的状态,而不是一个陷阱。
79
79
  - **实时匹配状态** —— 「模型背景」页会显示读到的是哪个模型、落在哪条规则上。
80
80
  - **导入 / 导出** —— 整套规则导出为 `dsh-background-by-model-theme.json`,图片全部内联;旧版本写出的文件仍可导入。
@@ -107,16 +107,16 @@
107
107
 
108
108
  - **中秋是农历八月十五**,按**北京时间**从中国农历解析,所以每年都动(2024-09-17、2025-10-06、2026-09-25 …);**国庆是 10 月 1–7 日**;两者撞上时中秋优先。
109
109
  - **没有图的节日会顺延**给模型规则,而不是把背景刷白。
110
- - **画是下载的,不随包发布** —— 每个节日一张压缩后的壁纸,钉在项目 [assets 仓库](https://github.com/HarmlessFunny/assets)的一个发行 tag 上,当天第一次读取时缓存到 `holiday-cache/<tag>/`。下载由日历把关,所以平常的日子一个字节都不取,此后的每一次启动都直接读盘。
110
+ - **画是下载的,不随包发布** —— 每个节日一张压缩后的壁纸,从**B 站图床**(`i0/i1/i2.hdslb.com`,三个边缘互为回落)取,当天第一次读取时缓存到 `holiday-cache/<哈希>/`。缓存目录名来自 URL 里那段**内容哈希**:换图就必须换地址,于是换目录、必然重下,没有需要记得去改的版本号。下载由日历把关,所以平常的日子一个字节都不取,此后的每一次启动都直接读盘。
111
111
  - **画和它的配色都不可以换** —— 节日槽位只读,颜色与满幅的「填充」都取自定义,主题导出也只带你的规则。
112
112
 
113
- ### 规则与图片各自的设置
113
+ ### 每条规则与每张图各自的设置
114
114
 
115
115
  - **壁纸** —— 每条规则一张或多张图;不轮换时规则画的是第一张。
116
116
  - **主题色** —— HSL 色轮、数值输入、**从本图提取**与吸管。它属于**图片**,没有自己颜色的图跟随系统主题,而不是继承规则色。
117
117
  - **布局模式** —— 适应 / 填充 / 拉伸 / 平铺 / 居中,逐图保存;规则没有图片时这排是禁用而不是隐藏。
118
118
  - **取景** —— 拖动平移、滚轮缩放,只在适应模式下可编辑,逐图保存,窗口缩放后保持一致。
119
- - **图片透明度** `0–100%`、**图片亮度** `0–200%`(`100%` 即原图)与**图片模糊** `0–60 px`,三者都按规则,并通过**同一条滑块 + 旁边的下拉框**切换调节项。它们只作用于图片层;「界面」页的透明度作用于界面各区域。
119
+ - **图片透明度** `0–100%`、**图片亮度** `0–200%`(`100%` 即原图)与**图片模糊** `0–60 px`,三者都**逐图保存**,并通过**同一条滑块 + 旁边的下拉框**切换调节项 —— 一张亮图和一张夜景图因此在同一组轮换里各有各的曝光。它们只作用于图片层;「界面」页的透明度作用于界面各区域。没有任何图片的规则这三项是禁用而不是隐藏。
120
120
 
121
121
  ### 全局设置(「界面」页)
122
122
 
@@ -126,12 +126,15 @@
126
126
 
127
127
  ### 推荐配置
128
128
 
129
- 「配置」页点一下就下载作者本人的整套配置 —— 规则、壁纸与界面设置 —— 来自项目的 assets 仓库。
129
+ 「配置」页点一下就换上作者本人的整套配置 —— 七条模型规则与那套界面数值**随插件内置**,只有七张壁纸从**B 站图床**下载。
130
130
 
131
- - **托管在数据目录自己的形状里**(`preset/preset.json`、`preset/theme-config.json`,每张壁纸一个 `modelbg-<slot>` 文件),所以它能在 GitHub 上直接编辑,发布它就是一次 `cp`。
131
+ - **规则不再是远端文件** —— 它编译在包里,和读它的代码同一个版本。所以这个按钮不需要 GitHub 能通,也不存在「远端配置比插件新」这种要拦的情况:能被安装的规则,永远是这份构建自己写的那些。
132
+ - **壁纸地址是一张常量表** —— 槽位 → BFS 路径,编译期就定死了。请求清单因此不是从网上下载来的,一次按错也不可能变成「按别人的清单去取 64 个文件」。
133
+ - **一个地址对应一份内容** —— BFS 的路径是上传内容的哈希,所以壁纸地址天然不可变,不像 git tag 那样能被重新指向别的字节。
134
+ - **`@3840w.webp` 是必须的** —— 它们上传的原图是 2.4–12 MB 的 PNG(九张合计 76 MB),加上这个后缀才是 250–365 KB 的 4K WebP,也正是现有数据目录里已经存着的那份字节(sha1 一致)。
132
135
  - **里面每一个界面数字都是插件本来就用着的值**,所以在新装上是「加上规则和图片」;而在你已经调过的数据目录上,它把这些也一起重置 —— 它是一整份配置,不是一个补丁。
133
136
  - **先下载、后提问**,所以确认框能如实说出手里有什么 —— 而清空在下载**之后**,绝不在之前。它是面板里唯一一张会**先问再删**的卡片。
134
- - **少一张壁纸就整体失败并报出是哪个槽位**,而且每个文件各自重试,已经下到的部分留给下一次。
137
+ - **少一张壁纸就整体失败并报出是哪个槽位**,而且每个文件各自重试(三个边缘 × 三轮),已经下到的部分留给下一次。
135
138
 
136
139
  ### 图片存储清理
137
140
 
@@ -141,6 +144,7 @@
141
144
  - **只删没人引用的** —— 当前规则用到的图一个字节都不动,配置本身也不写。清理完会重新读一次,而不是用减法算,所以删除失败的文件仍然算在里面。
142
145
  - **失败会点名** —— 一个文件删不掉不会让其余六个留下来,但也不会被说成「已清理」:提示里会列出是哪几个。
143
146
  - **不问确认** —— 要删的东西按定义没有任何规则引用,而且不是从一个能用的集合里挑出来的,所以「确认」在这里只教人不看就点。安全性放在**前置判断**上:配置读不出来时按钮是灰的(见下一条),而不是先把七个文件列出来再问一遍。
147
+ - **坏掉的主文件由备份作答** —— 主配置读不出来、而 `.bak` 能解析时,清理按**备份里的规则**走:那正是你马上会拿回的配置。两份都读不出来才是「读不到」,按钮这时候才是灰的。
144
148
  - **顺手清掉误入槽位的节日文件** —— `modelbg-h-*` 不属于任何配置(节日画在 `holiday-cache/` 里,那个槽位只读),所以它是唯一一个只有这条路径能清掉的残留。
145
149
  - **宿主是旧版时显示「读不到」而不是「0 B」** —— 两种「没有」必须长得不一样。
146
150
 
@@ -162,9 +166,10 @@
162
166
 
163
167
  | 文件 | 内容 |
164
168
  | --- | --- |
165
- | `theme-config.json` | 规则列表(每条规则含图片列表与轮换设置)+ 全局「界面」设置 + 全局切换效果 + 节日覆盖配置 |
169
+ | `theme-config.json` | 规则列表(每条规则含图片列表,每张图含自己的外观与轮换设置)+ 全局「界面」设置 + 全局切换效果 + 节日覆盖配置 |
170
+ | `theme-config.json.bak` | 上一份**写成功时**的配置。每次保存都是「先写临时文件、再改名覆盖」,并在覆盖前把当前这份合格的文件留成 `.bak`;主文件读不出来时就从这里恢复,而不是退回出厂默认。读不出且没有备份时,清理卡会拒绝删图 |
166
171
  | `modelbg-<slot>` | 一张图片,保存为原始字节、无扩展名 —— 一张图一个文件,所以一条有三张图的规则占三个槽位 |
167
- | `holiday-cache/<tag>/<asset>` | 从 CDN 下载的节日画,一个节日一个文件(`mid-autumn.webp`、`national-day.webp`),装在一个以**插件所钉的 assets 发行版**命名的目录里(`v0.1.0`)。tag 进了路径,所以钉了新 tag 的构建从一个空目录开始,而不是被告知上一代的字节就是答案;目录里的文件名仍然是**素材名**,刻意放在槽位命名空间之外,这正是节日槽位只读的原因。被取代的旧代会在新图落盘之后被清掉。删掉这里的文件也没关系,下次那个节日生效时会重新下载 |
172
+ | `holiday-cache/<哈希>/<asset>` | 从 B 站图床下载的节日画,一个节日一个文件(`mid-autumn.webp`、`national-day.webp`),装在一个以**该图内容哈希的前八位**命名的目录里(今天是 `42e22801`、`30e97211`)。哈希进了路径,所以换了图就是换了一个空目录,不可能被告知上一代的字节就是答案 —— 也没有需要记得去改的版本常量;目录里的文件名仍然是**素材名**,刻意放在槽位命名空间之外,这正是节日槽位只读的原因。被取代的旧代(包括 0.10 及更早的 `holiday-cache/<tag>/`)会在新图落盘之后被清掉。删掉这里的文件也没关系,下次那个节日生效时会重新下载 |
168
173
 
169
174
  ## 安装
170
175
 
@@ -224,14 +229,14 @@ pnpm test # 构建 lib/、类型检查、跑十三道检查
224
229
  - **`check:transition`** —— `resolveFadeMs` / `transitionPlan`:出厂默认仍然产出与旧版本逐字节相同的过渡串、每一条硬切路径都确实是硬切、「减少动态效果」是否决而非缩短,以及每种效果结束时都完全可见且没有残留缩放。
225
230
  - **`check:repaint`** —— `shouldRepaint`,包括那条**制造**赢家的编辑,以及任何一条写入路径不再询问、或轮换步骤不再重发界面调色板时报错。
226
231
  - **`check:code-plate`** —— 代码块底板:token 在 `:root` 与 `body` 上都有声明、浅色分支发出 banner、两块表面各读自己的插件变量。
227
- - **`check:image-mode`** —— 布局模式下放到图片上的那次抬升,以及某个控件编辑的是哪张图、渲染层读的又是哪张图。
232
+ - **`check:image-mode`** —— 布局模式与三个壁纸效果放到图片上的那次抬升,以及某个控件编辑的是哪张图、渲染层读的又是哪张图。
228
233
  - **`check:rules`** —— 选项卡背后那两个住在 `src/client/rules-view.ts` 的判定,以及布局所依赖的页面结构。
229
- - **`check:preset`** —— 推荐配置的重试策略与失败那一次给下一次留下什么,并验证两侧真的在用它们。
234
+ - **`check:preset`** —— 推荐配置本身(规则、槽位 ↔ 地址表、那一代常量)与重试策略,以及失败那一次给下一次留下什么,并验证两侧真的在用它们。规则现在编译在包里,所以这里也负责拦住「某条规则指向一个表里没有的槽位」这种只有发布后才会发现的错。
230
235
  - **`check:ui`** —— 两本字典键集一致、每个 `dab-…` 类都有样式、没有死键,以及每个 `var(--dsw-…)` 都是已安装主题发布过的 token。
231
236
  - **`check:watchdogs`** —— 一条心跳、按各自到期时间触发、隐藏即暂停、dispose 不留定时器,且入口之外没有模块自己起 interval。
232
237
  - **`check:scheme`** —— `data-ds-dark-theme` 的交接:插件只改自己写的那个值、清掉 token 时一起交还标记、方案变了会重绘。
233
238
  - **`check:store`** —— 存储清理的两个判定:`referencedSlots` 与 `orphanSlots`。钉住「正在用的图不会被当成孤儿」与「节日槽位的文件必须可清」,外加浏览器半边传的是**规则槽位**而不是整份配置。
234
- - **`check:node`** —— 把构建好的 node 半边通过真实的 RPC 处理器跑一遍(`DSH_HOME` 指向临时目录、`fetch` 换成桩):节日配置块、节日画的缓存与清理、镜像回落、逐图颜色抬升、全局切换效果、图片存储的用量报告与清理,以及推荐配置的整条链路。
239
+ - **`check:node`** —— 把构建好的 node 半边通过真实的 RPC 处理器跑一遍(`DSH_HOME` 指向临时目录、`fetch` 换成桩):节日配置块、节日画按内容哈希的缓存与清理、三个边缘的回落、逐图颜色与逐图效果的抬升、全局切换效果、配置的原子写与备份/恢复、图片存储的用量报告与清理,以及推荐配置的七张图(含旧清单方法的消失、槽位白名单、超时与重试预算)。
235
240
 
236
241
  ## 常见问题
237
242
 
@@ -242,7 +247,10 @@ pnpm test # 构建 lib/、类型检查、跑十三道检查
242
247
  匹配是先命中者胜,而不是最精确者胜:把更具体的规则往上拖,或调整顺序。
243
248
 
244
249
  **图片存在哪?**
245
- 在 `~/.dsh/.dsh-background-by-model-data/`:每张图一个 `modelbg-<slot>` 文件,外加 `theme-config.json`。
250
+ 在 `~/.dsh/.dsh-background-by-model-data/`:每张图一个 `modelbg-<slot>` 文件,外加 `theme-config.json` 与它的 `.bak` 备份。
251
+
252
+ **`theme-config.json.bak` 是什么?可以删吗?**
253
+ 是上一份**成功保存**的配置。每次保存都先写临时文件再改名覆盖,覆盖前把当时那份能解析的文件留成备份,所以主文件哪天读不出来(断电、崩溃、手滑编辑),插件会从备份恢复而不是退回出厂默认。可以删,代价是下次真的坏掉时就没有可恢复的那一份。
246
254
 
247
255
  **我开了轮换,背景却一直没变。**
248
256
  有三件事会拦住它:规则至少要有两张图;标签页必须可见;只有当前模型解析出的那条规则在轮换。
@@ -284,21 +292,24 @@ pnpm test # 构建 lib/、类型检查、跑十三道检查
284
292
 
285
293
  > 这里只列最近两个版本;更早的版本见 [CHANGELOG.zh.md](./CHANGELOG.zh.md)。
286
294
 
287
- ### v0.9.2
288
-
289
- - **「配置」页多了一张「图片存储」卡片** —— 先报出可清理多少、涉及几个文件,再给你一个清理按钮;只删没有规则引用的文件,配置与节日画都不动,删不掉的文件会点名。
290
- - **`check:store`,于是现在十三道校验** —— 两份 README 此前一直写着「十道」,`check:watchdogs` 与 `check:scheme` 从 v0.8.1 起就没列进去,这次一并补上。
291
-
292
- ### v0.9.1
295
+ ### v0.11.0
293
296
 
294
- - **三条壁纸滑块合并成一条,旁边用下拉框切换** —— 选项改叫图片透明度 / 图片亮度 / 图片模糊,下拉框与按钮同色,滑块下的说明文字删掉。
297
+ - **下载源换成 B 站图床** —— 推荐配置的七张壁纸与中秋、国庆两张节日画,全部改从 `i0/i1/i2.hdslb.com` 取,三个边缘互为回落;README 里那三张截图仍留在原处,它们不参与运行时。
298
+ - **推荐配置的规则改成随插件内置** —— 七条模型规则与那套界面数值编译进包里,按钮只下载七张图。于是它不再需要 GitHub 能通,也不再有「远端配置比插件新」这道闸和 `preset.json` 清单 —— 能被安装的规则永远就是这份构建自己写的那些。
299
+ - **请求清单变成一张常量表** —— 槽位 → BFS 路径编译期定死,而 BFS 路径是上传内容的哈希,所以壁纸地址天然不可变,不像 git tag 那样能被重新指向别的字节。
300
+ - **地址带 `@3840w.webp`** —— 上传的原图是 2.4–12 MB 的 PNG(九张合计 76 MB),加上这个后缀才是 250–365 KB 的 4K WebP,与现有数据目录里那几份字节的 sha1 完全一致。
301
+ - **节日画的缓存目录按内容哈希分代** —— 从 `holiday-cache/<tag>/` 改成 `holiday-cache/<哈希前八位>/`。换图就换地址、换目录、必然重下,那个「记得顺手把版本号也改了」的坑从此不存在;旧的 tag 目录会在新图落盘后由同一次清扫带走。
302
+ - **`SCHEMA_VERSION` 仍是 10** —— 这次没有字段进出配置形状:变的是这些数值从哪来,不是它们长什么样。
303
+ - **门禁** —— `check:preset` 改钉内置配置本身(规则顺序、槽位 ↔ 地址表、地址里的 webp 变换与内容哈希、缓存分代),`check:node` 重写节日与推荐配置两节(三个边缘的回落与重试预算、旧 `fetchPresetConfig` 方法已不存在、槽位白名单、tag 目录被清扫),`check:scheme` 第 3 节不再联网。
295
304
 
296
- ### v0.9.0
305
+ ### v0.10.0
297
306
 
298
- - **RPC 通道是插件自己在 `webServer` 上的 prefix 路由** —— envelope、请求上限与鉴权围墙留在插件这侧,每个请求都询问 `requestRejection`。
299
- - **所有周期性检查合并成一个调度器** —— 模型轮询、主题看守、节日换日与延迟恢复共用一条时钟,标签页隐藏时暂停,一个 dispose 全停。
300
- - **浏览器半边入口只剩接线** —— 皮肤、看守、轮换、启动、模型状态、取色与主题文件各自成模块。
301
- - **导出主题不再取消自己的下载**,注释也只讲代码为什么是现在这样、不讲它曾经是什么样。
307
+ - **配置改成原子写,并留一份 `.bak`** —— 保存先写临时文件再改名覆盖,覆盖前把当前能解析的那份留成备份;主文件坏了就从备份恢复,而不是退回出厂默认、再被你下一次拖动覆盖掉。
308
+ - **透明度、亮度与模糊改成逐图保存** —— 与布局模式、取景、主题色同一层;schema-9 的规则级三个值读取时抬到每张图上,升级不改屏幕。
309
+ - **`SCHEMA_VERSION` 升到 10** —— 字段搬了位置,旧宿主会静默丢掉它们,所以必须让浏览器半边知道。
310
+ - **模糊的壁纸不再在视口四周画一圈黑边** —— 图层盒子向屏幕外各扩 2σ 再裁掉,取景位置补回同样的距离,模糊因此取样到屏幕之外,边缘不再透出背后页面的颜色。
311
+ - **透明度、亮度或模糊不同的两张图之间切换现在有动画了** —— 每个图层记住自己那张图的滤镜,壁纸透明度也跟着过渡走,不再直接切。
312
+ - **拖动这三个滑块不再每帧整块重绘** —— 拖动只写那个字段自己的属性,松手才走一次完整重绘。
302
313
 
303
314
  ## Star History
304
315