dsh-background-by-model 0.6.0 → 0.7.2
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 +41 -1
- package/CHANGELOG.zh.md +40 -1
- package/README.md +63 -21
- package/README.zh.md +65 -23
- package/lib/client.js +1413 -204
- package/lib/client.js.map +1 -1
- package/lib/index.js +150 -21
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
> The full release history lives here; the two most recent releases (v0.
|
|
3
|
+
> The full release history lives here; the two most recent releases (v0.7.2 / v0.7.1) are also listed in [README.md](./README.md#recent-optimizations).
|
|
4
|
+
|
|
5
|
+
## v0.7.2
|
|
6
|
+
|
|
7
|
+
- **The filmstrip IS the control: drag to reorder, double-click to show** — the order used to be reachable only through a pair of ↑ / ↓ arrows and a "make first" button in the row below, which is a second and worse way to say "put this one there" when the five thumbnails already show the order they are in. Grab any tile and drop it where it belongs: the drop point is a 2px bar drawn INSIDE the tile it precedes, because a bar that took up room would push around the very tiles the pointer is being measured against. Which picture is on screen is the strip's OTHER gesture, and it changes no order and writes nothing at all — the image being shown is runtime state, not a setting, so it is not saved and the next boot starts at the rule's first image again. A double-click on a tile of a rule that is not painting says why (reusing "only the active rule can step the background") instead of leaving a double-click that looks broken, and both gestures are named in the tile's tooltip.
|
|
8
|
+
- **The order and "which one is on screen" no longer interfere with each other (this one is a fix)** — both old actions (`moveRuleImage`, `setCurrentImage`) ended by moving the image index onto the image they had just moved, so tidying the list swapped the wallpaper as well: with the rotation off the index is sticky, which is exactly the state this was met in ("even with the rotation off, the background can change when I adjust the order, and it can happen by dragging or by the buttons"). `moveRuleImageTo(id, slot, to)` replaces both of them: it remembers the SLOT being painted before the splice and puts the index back on it afterwards. A reorder is not a change of subject — the three buttons left with the two uncalled actions behind them, and `check:repaint`'s list follows.
|
|
9
|
+
- **The tile that is on screen lost its ring and kept its dot** — `.is-sel` already rings the tile that is open for editing, so a second ring put two "selected" tiles in a row of near-identical thumbnails, which is the one question a filmstrip must not leave open. Being on screen is the dot, and nothing else.
|
|
10
|
+
- **Every image of a batch upload gets its own color** — a theme color belongs to an IMAGE, so an upload carrying several pictures has several palettes to fill, and the extraction was addressed by POSITION: `addRuleImages` appended the whole batch and then asked for a color for `rule.images[rule.images.length - 1]` alone. That is the right answer for the one-file upload this action grew up with, and silently the wrong one for every batch — the pictures before the last kept no color of their own and stayed on the system theme, with nothing in the interface to say why. It is now addressed by the slots the batch itself just appended, so which pictures get themed no longer depends on where they landed in the rule, nor on which one the card happens to select.
|
|
11
|
+
- **Copy trimmed, and the framing button moved** — the "Rules" heading and the priority sentence under it, and the sentence explaining the automatic extraction, are gone (the panel's title introduces the rules and the `fallback` badge on the first card states the priority; the switch and its own label stay). The labels lost their nouns: `Image 1 / 5` for `当前第 1 / 5`, and `Add` / `Replace` / `Remove` for `添加图片` / `替换这张` / `移除图片`. `Edit position` (`编辑位置`) moved out of the image-action row to the end of the layout row — what it opens edits how the picture fills the area, which is what those five modes are about — and it stays last rather than between two chips because the modes are mutually exclusive and it is not a sixth one. It still appears only in **fit**, because dragging the framing is what fit means.
|
|
12
|
+
- **Gates** — `check:repaint` gained the batch section (the batch form must walk every slot it was handed, an upload must record the slots of its own batch and theme all of them, and the positional `maybeAutoExtract(id, rule.images[…])` must not come back), and `moveRuleImageTo` joins the actions required to sample the winner and close with `repaintIfMoved` while the two deleted actions leave it. `check:ui` covers the new strings and the new class (`.dab-strip-bar`, plus the `is-dragging` state), and requires the three keys nothing renders any more to leave both dictionaries in the same commit.
|
|
13
|
+
|
|
14
|
+
## v0.7.1
|
|
15
|
+
|
|
16
|
+
- **The theme color moved down to the image** — a rule is a rotation through several pictures, and pictures collected for different moods do not share one accent. Every image now carries its own `color: [h, s, l] | null`, and the whole color section — wheel, numeric inputs, inspiration palette, **Extract from this image**, eyedropper and **Follow system theme** — edits the image selected in the filmstrip. The controls gained one line that names their target ("Applies to the selected image: 2 / 3") and the filmstrip marks the images that already carry a color of their own (with the hex on hover), because a section that silently retargets is exactly how "I changed the color and nothing happened" starts. No new control and no layout change: the filmstrip was already the way to pick a picture, and it was already where framing is chosen.
|
|
17
|
+
- **A rule's own `color` now means exactly one thing: what an image-less rule paints** — it is deliberately NOT a default for the rule's pictures. An image with no color of its own follows the **system theme** rather than inheriting the rule's, which is what makes a rotation through a green picture, a red one and a system-themed one expressible at all: with inheritance, clearing a picture's color would only hand it back to the rule's, and the clear button would look broken (there would also be no way to reach the system theme for a rule that has both a color and pictures). Clearing one image's color therefore clears only that image; the rule keeps its own, ready for the state where it is the only thing left to paint. The panel's two branches follow the same rule: with an image to address, the controls edit the image; with `images: []`, they edit the rule.
|
|
18
|
+
- **Nothing loses its theme on upgrade, and the distinction is one key** — an image entry written before this release has no `color` key at all, and that is the whole test `normalizeImage` uses: a missing key means "this picture never had a color of its own", so the rule's color is lifted onto it (once, in the shared sanitizer, so both halves lift identically and the file heals on the first write); an explicit `null` means "cleared on purpose" and is never re-inherited over. That is also why every write emits the key even when the value is null — writing "no color" as an absent key would re-inherit the rule's color on every single load. A config predating the field therefore looks identical after the upgrade, and the pre-0.7 single-`slot` shape goes through the same lift (synthesized rather than read).
|
|
19
|
+
- **A rotation step re-emits the interface palette, and that is the bug this feature would otherwise ship with** — the step only repainted the wallpaper, so a per-image color existed in the config and in the panel while the interface behind it kept the previous picture's palette until *something else* re-ran an apply: dragging the color wheel, or switching models. That is the symptom this plugin already had once, verbatim ("it only updates when I touch the color wheel"), so the palette apply is now one function (`applyPalette`) called by both a model switch and every step of the rotation — the timer's tick and the manual **Next image** button share the single `paintImage` funnel that paints a step. `pnpm check:repaint` fails if either `paintImage` or `applyActive` stops going through it, or if the palette stops reading the color in force (`activeColor`: the painted image's color, else the rule's).
|
|
20
|
+
- **Holidays carry their palette per image too** — a festival's fixed color is written on the holiday *and* on its image, and `normalizeHolidayRule` forces both from the definition on every read (a hand-edited value in either place is replaced). The shape is therefore ready for a holiday that rotates through several pieces of art with a palette each, as a change to `HOLIDAYS`, its assets and the node half's slot→asset map rather than a second code path in the render layer.
|
|
21
|
+
- **The config shape moved to 4, and an older host still cannot eat a profile** — `images[].color` is unknown to a schema-3 `normalizeImage`, which REBUILDS each image entry from the keys it knows: the color would live in memory until the next reload and then be gone, silently and with nothing in the host log to explain it. `SCHEMA_VERSION = 4` is published on every `read`, and the client's `>=` comparison puts a host announcing less into the hold-writes path, which logs once and says so in the panel ("restart DSH"). Theme exports move to format 5; 4 and older still import, with the lift above supplying the colors they never had.
|
|
22
|
+
- **A rule with no picture has exactly one upload target** — the big empty preview tile *is* the button now (a real `<button>`, so it is focusable and keyboard-reachable; click it or drop files onto it), and the filmstrip is not rendered at all while the rule holds nothing: it contained nothing but a second "add image" tile, and a filmstrip is a list of pictures — a state with none does not have one. The drop handler moves up with it rather than disappearing, because hiding the strip would otherwise take drag & drop away from the state every rule starts in. Nothing else moved: the labelled **Add images** / **From URL** buttons stay where they were, the empty-state sentences are unchanged, and with pictures present the preview is still deliberately inert (dropping on the strip adds, and *replace* stays an explicit labelled action, which is what keeps a preview from swallowing a click the user did not mean).
|
|
23
|
+
- **Gates** — `check:node` gained the per-image color shape end to end through the real RPC handler: the lift in both directions (an absent key inherits the rule's color, an explicit `null` does not), independent colors surviving the round trip in order, holidays forcing both copies, the announced schema moving to 4, and `null` landing on disk as a key rather than as an omission. `check:repaint` gained the palette-on-a-rotation-step section above, and `setImageColor` — the new write path — is now one of the actions it requires to sample the winner and close with `repaintIfMoved`. `check:ui` covers the three new strings and the two new classes (`.dab-strip-dot`, `.dab-rule-thumb-add`), as it covers every other one.
|
|
24
|
+
|
|
25
|
+
## v0.7.0
|
|
26
|
+
|
|
27
|
+
- **Multi-image rotation, per rule — and off by default** — a rule now owns an ordered list of images (`images: [{ slot, bgState }]`) and can cycle them on its own rhythm: 10 s / 30 s / 1 min / 5 min / 30 min / 1 h, or any custom dwell from 5 s to 24 h, **in order** or **shuffle**. It ships OFF because rotating spends real bandwidth, memory and battery, and a default that quietly animates a wallpaper nobody asked to animate is a default that decided for the user.
|
|
28
|
+
- **A second trigger that needs no timer at all** — *also step on a model switch* advances the image once every time the model changes and lands on that rule. For a rule that should simply look different each time, that is the whole feature: nothing is scheduled, nothing wakes up, and the image only changes when the user does something.
|
|
29
|
+
- **Shuffle never lands on the image already on screen** — the picker is uniform over every index *except* the current one, because a tick that leaves the picture unchanged is indistinguishable from a tick that never fired — the one failure this feature could produce with no error anywhere. `pnpm check:rotation` walks the whole support of the roll for lists of 2, 3 and 7 images rather than sampling it once, and also pins the sequential walk's wrap, out-of-range and fractional indices, and that "rotating" is never claimed for a rule with fewer than two images or a non-finite interval.
|
|
30
|
+
- **One timer for the plugin, aimed at the active rule, re-armed by signature** — a timer per rule would burn wakeups on rules that are not painting anything, and only one rule can be on screen. It is (re)armed only when the signature of its own inputs changes — rule id, enabled flag, interval, order, image count, tab visibility — so the dozens of full re-applies a slider drag triggers can never restart the dwell (with a naive "re-arm on apply", dragging the opacity slider for 30 seconds would mean the rotation never advances during it). A hidden tab stops the rotation with the pixels and resumes on a fresh interval, instead of painting three times into a tab nobody is looking at.
|
|
31
|
+
- **The picture changes; the rule does not** — theme color, layout mode, wallpaper opacity and blur stay the rule's, and the transition is the same two-layer cross-fade a model switch already used. A rule can ask for a longer blend than a switch, or `fadeMs: 0` for a hard cut — which is kept as a real value rather than read as "absent".
|
|
32
|
+
- **Framing moved onto the image, where it belongs** — a crop is a property of a picture: two wallpapers of different aspect ratios share the rule's mode, color, opacity and blur, but one `fit` framing computed for the first is simply wrong for the second. Each image carries its own `bgState` and the background editor edits the image selected in the strip.
|
|
33
|
+
- **The single thumbnail became a filmstrip** — add several images at once (**multi-select**, **drag & drop**, or **one URL per line**, fetched sequentially so N downloads do not interleave with no progress to show), click one to edit it, reorder it, promote it to **first** (which is what the rule paints when nothing rotates), **replace this image** in place (which keeps its position in the rotation, so it is labelled apart from adding), or remove it.
|
|
34
|
+
- **A rule may hold NO image, and that has to be a real state** — the first cut of this feature refused to remove a rule's last picture and cleared its bytes "instead", which left the entry itself behind: a blank tile at position 1 of the filmstrip that could never be deleted, so the next upload landed at position 2 and the rule kept painting nothing while the preview showed an empty frame. Removing the last image now empties the list. A brand-new rule starts in that state too (no slot is allocated until the first picture), and a hand-edited list whose entries are all unusable lands in it rather than being conjured back into a broken slot. Only a rule that names no image anywhere — no list, no legacy slot — is dropped by the sanitizer.
|
|
35
|
+
- **A rule with no image left keeps painting from its theme color** — an empty list is not "this rule is off", which is what the first cut made of it: usability only asked whether an image was there, so a rule whose pictures had all been removed **stopped matching altogether** — its own theme color, the wheel, the swatches and the clear-to-system-theme button all became decoration (the reported symptom was "the theme color does nothing") — and the models it used to match were quietly handed to the **next** rule: empty the fallback rule and another model's wallpaper appears. Usability is now *an image, or a theme color of its own* (`ruleCanPaint`): such a rule still matches, still serves as the fallback, and simply paints the interface instead of a wallpaper; only a rule with neither (a freshly added one) is skipped. The card's copy now says which of the three real states it is in — no image but a color, neither, or an image whose bytes have not arrived — where one sentence used to claim "this rule has no image" for all three, and the "extracts from this image" hint no longer appears when there is no image to extract from.
|
|
36
|
+
- **Every edit that can move the winner takes effect at once, and only an edit that cannot may skip the repaint** — `color`, `enabled` and `match` all feed which rule wins, but each write path asked its own narrow question ("is this the rule that is painting right now?") before re-running the resolution, so only an edit to the rule that was *already* winning repainted. The edits that are not already winning are exactly the ones that **create** the winner: the first picture of a rule that was empty (which is what makes it paintable at all — see `ruleCanPaint`), and the first or auto-extracted color landing on it. Those landed in the config and in the panel's own readout while the interface behind it kept the previous rule, which was reported as "the update is never timely, it only appears when I move the color wheel or switch models" — dragging the wheel or a model switch was what finally re-ran the resolution. Every path now samples the winner before its mutation and compares it after (`repaintIfMoved`, over the pure `shouldRepaint` in `src/client/repaint.ts`), which also repairs a tracker that had gone stale: an edit to a rule that *is* the winner repaints even if the page never recorded it as active. `pnpm check:repaint` walks that decision's truth table — both directions of the winner moving, and the cases that must stay cheap — and fails if any of the nine write paths in the entry file — the auto-extraction that lands after an upload included — stops asking it. The live check that pinned this down drove a throwaway rule through the exact sequence (create empty → move to the top → upload one picture → touch nothing): with the old test the paint never moved until something else did, with this one it moved in ~280 ms, and the picture's own auto-extracted color came with it.
|
|
37
|
+
- **An older host process can no longer eat a profile — the check is a version comparison, which is why the shape moved to 3** — the browser half and the node half are loaded separately, so updating the package and merely refreshing the page can pair a NEW client bundle with a host process still running the PREVIOUS node half. That older sanitizer knows nothing about `images` and drops every rule that has one — silently, on the first slider drag. The node half therefore publishes the config shape it writes (`schema` on every `read`), and a client that finds an older answer **holds every config write** (one funnel, `canWriteConfig` in `src/client/rpc.ts`), logs once, and says so in the panel: "restart DSH". Edits that silently do not persist are worse than edits that visibly wait for a restart; edits that silently delete rules are worse than both. During 0.7.0's own development this gate did let one through: the version was 2, and the running 0.7 node half reported `schema: 2` while being a build from **before the empty-rule fix**, so a write that emptied a rule was allowed through and the host erased those rules from disk. The number therefore now means "**an empty image list is legal**" (`SCHEMA_VERSION = 3`), not merely "rules carry `images`": any host announcing less than that lands in the hold-writes state.
|
|
38
|
+
- **An emptied rule and an upload after it** — the picture you add is the picture you are then looking at (the card moves its selection to the first newly added image, and the strip, the big preview and the position readout all follow), instead of leaving the panel showing whatever used to be selected.
|
|
39
|
+
- **Only the images that are needed are loaded** — boot hydrates each rule's **first** image, which is exactly what it hydrated per rule before multi-image existed, plus today's holiday and the image the current model paints. The rest arrive when a card is expanded or when the rotation is about to need one. Hydrating every image of every rule would mean twenty full-size data URLs over this plugin's RPC channel before the first frame on a five-rule setup with four pictures each; the slot list the store reports is likewise no longer hydrated, because an orphaned slot nothing references has no card to appear on.
|
|
40
|
+
- **An older host process can no longer eat a profile** — see the version-comparison entry above: the shape's version number is what a stale host is judged by, and it now stands for "an empty image list is legal" as well.
|
|
41
|
+
- **Older configs still load, and older exports still import** — `normalizeRule` accepts both shapes: a rule's single `slot`/`bgState` pair is lifted into `images: [{ slot, bgState }]` on read, duplicates within a rule are dropped, and an unusable slot is dropped. Theme exports move to **format version 4** and now carry, and restore, every image of every rule rather than just the first; version 3 files still import.
|
|
42
|
+
- **Per-image slots are independent, and removing a rule releases only what it owns** — a rule with three images owns three `modelbg-<slot>` files; `nextSlot` scans every slot in the config (rules *and* holiday entries) so a second image can never collide with an existing one, and deleting a rule reclaims each of its slots only when nothing else references it.
|
|
43
|
+
- **The node half reports nested drift too, and migration writes the new shape** — `warnUnknownConfigKeys` now compares `rules[].rotate` and `rules[].images[]` one level deeper, which is where a field that only the client bundle knows would otherwise disappear in silence, while the legacy `slot`/`bgState` names are explicitly exempt because the sanitizer lifts them on purpose. The pre-0.3 migration writes an image list directly instead of leaving a legacy shape on disk until the first write.
|
|
4
44
|
|
|
5
45
|
## v0.6.0
|
|
6
46
|
|
package/CHANGELOG.zh.md
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
-
> 这里保留完整的历史记录;最近两个版本(v0.
|
|
3
|
+
> 这里保留完整的历史记录;最近两个版本(v0.7.2 / v0.7.1)同时列在 [README.zh.md](./README.zh.md#近期优化) 里。
|
|
4
|
+
|
|
5
|
+
## v0.7.2
|
|
6
|
+
|
|
7
|
+
- **胶片条自己就是控件:拖拽排顺序,双击换画面** —— 顺序以前只能靠下面那排 ↑ / ↓ 和一个「设为第一张」按钮改,而五个缩略图本来就把顺序摆在那里,那对箭头是同一件事的第二种、更差的说法。现在抓住任意一张拖到新位置:落点画成一根 2px 竖条,画在目标格子**内部**,因为一块占位的竖条会把指针正在测量的那些格子推走。而「哪张在屏上」是胶片条的**另一个**手势 —— 双击它。它不改顺序,也**不写任何配置**:在屏哪张是运行时状态而不是设置,所以它不会被保存,下次启动照旧从规则的第一张开始。在非生效规则的格子上双击会说明原因(复用现成的那句「只有正在生效的规则能换画面上的图」),而不是留下一个看起来坏了的双击;两个手势都写在格子的悬停提示里。
|
|
8
|
+
- **顺序和在屏的是哪张,从此互不干涉(这一条是修复)** —— 旧的两个动作(`moveRuleImage`、`setCurrentImage`)都以「把图片下标挪到刚被移动的那张图上」结尾,于是整理列表会把壁纸一起换掉:轮换关着的时候下标是粘住的,而这正是最容易撞见它的状态(「我调整顺序时,即便关了轮换,当前背景也可能会变,拖拽或点按钮调整顺序都有可能发生」)。新的 `moveRuleImageTo(id, slot, to)` 取代了它们两个:摘出之前先记下**正在画的那个槽位**,插回去之后把下标放回它身上。重排不是换主题 —— 三个按钮连同它们背后两个不再被调用的动作一起删掉了,`check:repaint` 的清单同步。
|
|
9
|
+
- **在屏那张不再有边框,只留圆点** —— `.is-sel` 已经给「正在编辑的那张」画了圈,再给「正在显示的那张」画一个,就成了一排几乎一样的缩略图里同时有两个「选中」,而这是胶片条唯一不能留给人猜的问题。现在在屏 = 右上角那个圆点,仅此而已。
|
|
10
|
+
- **一次上传的多张图,每一张都会各自取色** —— 主题色属于**图片**,所以一次带多张的上传有好几个调色板要填,而取色是**按下标**寻址的:`addRuleImages` 先把整批追加完,然后只对 `rule.images[rule.images.length - 1]` 取色。这对它长大时面对的单张上传是对的,对批量则静默地错 —— 最后一张之前的那些图没有自己的颜色、留在系统主题上,而界面上什么都不会说。现在按**这一批自己刚追加的那些槽位**寻址,所以「哪些图会被取色」不再取决于它们落在规则的哪个位置,也不取决于卡片当时选中了哪一张。
|
|
11
|
+
- **卡片文案精简,「编辑位置」换了位置** —— 「规则列表」标题和它下面那句优先级说明、以及自动取色旁边那句解释都删了(面板标题已经介绍了规则,第一张卡上的 `兜底` 徽章说明了优先级;开关和它自己的标签留着)。标签去掉多余的名词:`当前第 1 / 5` → `第 1 / 5`,`添加图片` / `替换这张` / `移除图片` → `添加` / `替换` / `移除`,英文同步。`编辑位置` 从「图片动作」那一行移到**布局模式那一行的末尾**:它打开的东西编辑的是图片如何填满这块区域,正是那一行五个模式在说的事;它留在末尾而不是插在两个模式之间,因为那五个是互斥的,它不是第六个。它仍然只在**适应**下出现 —— 拖拽构图就是适应在做的事。
|
|
12
|
+
- **门禁** —— `check:repaint` 新增一节钉住批量取色:批量形式必须遍历它拿到的每一个槽位、上传必须记录自己这批的槽位并给它们全部取色、按下标寻址的 `maybeAutoExtract(id, rule.images[…])` 不许回来;`moveRuleImageTo` 进入「每个写入路径都必须先采样 winner、再以 `repaintIfMoved` 收尾」的清单,两个被删掉的动作则从中移出。`check:ui` 覆盖新文案与新类(`.dab-strip-bar`,以及 `is-dragging` 状态),并且要求不再有调用者的那三个字典键在同一个提交里离开两份字典。
|
|
13
|
+
|
|
14
|
+
## v0.7.1
|
|
15
|
+
|
|
16
|
+
- **主题色下放到图片** —— 一条规则是一组图片的轮换,而这些图往往是为不同心情收的,不该共用一个强调色。每张图现在带自己的 `color: [h, s, l] | null`,整个调色区——色轮、数值输入、灵感色板、「从本图提取」、吸管、「跟随系统主题」——作用于**胶片条里选中的那张图**。控件上方多了一行写明目标(「作用于选中的图片:2 / 3」),胶片条标出哪些图已经各自有颜色(悬停显示色值),因为一个会悄悄换目标的区块,正是「我改了颜色却没反应」的开端。没有新控件、也没有布局改动:胶片条本来就是选图的入口,也本来就是逐图取景的入口。
|
|
17
|
+
- **规则自己的 `color` 只剩一个含义:一张图都没有时它画什么** —— 它刻意**不是**该规则图片的默认值。没有自己颜色的图**跟随系统主题**,而不是去继承规则色;正是这一点才让「绿的、红的、跟随系统主题」这样一组轮换可以表达——若改成继承,清空某张图的颜色只会把它交还给规则色,「跟随系统主题」按钮看起来就成了失效的按钮(而且一条既有颜色又有图片的规则将无处抵达系统主题)。所以清空某张图的颜色只影响那一张;规则自己的颜色留着,等「只剩它能画」的那个状态。面板的两个分支遵循同一条规则:有图可指就编辑图,`images: []` 就编辑规则。
|
|
18
|
+
- **升级不丢主题色,而区分只靠一个键** —— 这个版本之前写下的图片条目**根本没有** `color` 键,这就是 `normalizeImage` 的全部判据:缺键 = 「这张图从来没有自己的颜色」,于是规则色被抬升到它身上(只做一次,且在共享 sanitizer 里做,两半边行为一致,文件也会在第一次写入时自愈);显式 `null` = 「主动清空过」,任何时候都不会被重新继承覆盖。这也是为什么每次写入都会带上这个键、哪怕是 null——把「没有颜色」写成缺键,等于每次加载都把规则色继承回来。因此这份字段出现之前的配置升级后观感完全一致,0.7 之前那对单图字段(`slot` / `bgState`)也走同一次抬升(只是那一条是构造出来的,不是读出来的)。
|
|
19
|
+
- **轮换每一步都重发界面调色板,否则这个功能一出生就带着那个 bug** —— 换图原本只重画壁纸,于是「逐图主题色」只存在于配置和面板里,界面配色要等**别的**事情顺手重跑一次 apply 才跟上:拖动调色盘,或者切换模型。这正是这个插件已经踩过一次的现象,一字不差(「更新的不太及时,只有我动调色盘或者切模型的时候才出现」)。所以调色板的施加被收成一个函数(`applyPalette`),模型切换和轮换的每一步都走它——定时器 tick 与手动「下一张」共用那个负责画一步的 `paintImage` 出口。只要 `paintImage` 或 `applyActive` 不再经过它、或者它不再读取「当前生效的颜色」(`activeColor`:正在显示的那张图的颜色,否则规则色),`pnpm check:repaint` 就会报错。
|
|
20
|
+
- **节日配色同样逐图** —— 节日的固定配色同时写在节日条目**和它的图片**上,`normalizeHolidayRule` 每次读取都用定义强制覆盖两处(任一处手改都会被替换回来)。所以「节日多图轮换、每张带自己的配色」已经只差 `HOLIDAYS`、它的素材,以及 node 半边的「槽位 → 素材」映射,而不需要在渲染层再开一条代码路径。
|
|
21
|
+
- **配置格式升到 4,旧宿主依旧吃不掉配置** —— `images[].color` 对 schema 3 的 `normalizeImage` 是未知字段,而它会**按自己认识的键重建**每个图片条目:颜色会一直在内存里活到下一次重新加载,然后悄无声息地消失,宿主日志里也什么都不会说。`SCHEMA_VERSION = 4` 在每次 `read` 时公布,客户端用 `>=` 比较,报出更低版本的宿主会进入扣住写入的分支:日志提示一次,面板里说明(「重启 DSH」)。主题文件升到格式 5;4 及更早仍可导入,它们从来没有的颜色由上面那次抬升补上。
|
|
22
|
+
- **没有图片的规则只剩一个上传入口** —— 上方那块大预览**本身就是按钮**了(真正的 `<button>`,可聚焦、可键盘触发;点它上传,也可以把文件拖上去),而胶片条在一张图都没有时**完全不渲染**:它里面除了一块「添加图片」再没有别的东西,而胶片条是一组图片的列表,一张图都没有的状态下它没有存在的理由。拖放处理跟着一起上移而不是消失——把胶片条藏起来,不该把每条规则诞生时都要用的拖放也一起拿走。其它都没动:带文字的 **添加图片** / **从网址** 按钮还在原位,空状态那几句话一字未改;有图片时预览依旧刻意保持惰性(拖到胶片条是新增,「替换这张」仍是显式的带文字按钮——正是这一点让预览不会吞掉用户无意的点击)。
|
|
23
|
+
- **门禁** —— `check:node` 通过真实 RPC 处理器把逐图主题色这套形状端到端跑了一遍:抬升的两个方向(缺键继承规则色、显式 `null` 不继承)、颜色经过文件往返后各自独立且保持顺序、节日两处颜色都被强制、公布的 schema 升到 4,以及 `null` 落盘时是**键**而不是被省略。`check:repaint` 多了一节「轮换步骤必须重发调色板」,而新写入路径 `setImageColor` 也进入它要求「先采样 winner、再以 `repaintIfMoved` 收尾」的清单。`check:ui` 覆盖那三条新文案与两个新类(`.dab-strip-dot`、`.dab-rule-thumb-add`),跟其它每一条一样。
|
|
24
|
+
|
|
25
|
+
## v0.7.0
|
|
26
|
+
|
|
27
|
+
- **每条规则多图轮换 —— 默认关闭** —— 规则现在持有一组有序图片(`images: [{ slot, bgState }]`),可以按自己的节奏循环:10 秒 / 30 秒 / 1 分钟 / 5 分钟 / 30 分钟 / 1 小时,或 5 秒 – 24 小时之间的任意秒数;顺序可选**按顺序**或**随机**。默认是关的:轮换真的要花流量、内存和电,一个偷偷让壁纸动起来的默认值,等于替用户做了决定。
|
|
28
|
+
- **第二种触发完全不需要定时器** —— 「切换模型时也换一张」:每次切换模型落到这条规则上就前进一张。对一条「每次都想看到不一样」的规则来说,这就是全部功能:不排期、不唤醒,只在用户做事的时候换图。
|
|
29
|
+
- **随机绝不会抽到当前正在显示的那张** —— 挑选在**除当前这张之外**的所有下标上等概率进行,因为一次「图没变」的 tick 与一次「压根没触发」的 tick 完全无法区分 —— 而这正是这个功能唯一可能悄无声息失败的方式。`pnpm check:rotation` 对 2、3、7 张图的列表把整个随机取值范围**穷举**了一遍(不是抽一次样本),同时钉住顺序巡回的环绕、越界与小数下标,以及「只有一张图或间隔非有限值时报出的绝不是『正在轮换』」。
|
|
30
|
+
- **全插件只有一个定时器,始终对准生效规则,按签名重排** —— 每条规则一个定时器会让根本没在画的规则白白唤醒,而屏幕上同时只可能有一条规则的图。它只在**排期真正依赖的那些输入**组成的签名变化时重排:规则 id、开关、间隔、顺序、图片数量、标签页可见性。所以拖动滑块导致每秒重跑几十次 apply 也不会把停留计时反复清零(若按「每次 apply 就重排」的写法,把透明度滑块拖上 30 秒就意味着这 30 秒里轮换永远不会前进)。标签页隐藏时轮换跟着画面一起停下,回到前台时按全新间隔重新计时,而不是对着没人看的标签页画三次。
|
|
31
|
+
- **换的只有画面,规则本身不动** —— 主题色、布局模式、背景透明度与模糊度都留在规则上,过渡用的就是切换模型时那套双层交叉淡入。规则还能要求比切换更慢的淡入,或者 `fadeMs: 0` 直接硬切 —— 而 0 是一个真实取值,不会被当成「没设置」。
|
|
32
|
+
- **取景移到图片上,这才是它该在的地方** —— 构图属于某一张图:两张宽高比不同的壁纸共享规则的模式、颜色、透明度与模糊度,但为第一张算出来的那套适应取景,用在第二张上就是错的。每张图各自带一份 `bgState`,背景编辑器编辑的是胶片条里选中的那一张。
|
|
33
|
+
- **单图缩略图变成胶片条** —— 可一次加多张(**多选文件**、**拖放**,或**一行一个网址**批量粘贴,逐个下载以免 N 个请求互相穿插、连进度都没法显示)、点选要编辑的那张、排序、设为**第一张**(不轮换时规则画的就是它)、**原地替换这张**(替换会保住它在轮换里的位置,所以它与「添加」分开命名)、或删除。
|
|
34
|
+
- **规则可以一张图都没有,而这是一个必须成立的状态** —— 这个功能的第一版拒绝删除最后一张,转而「清空它的字节」,结果留下的正是条目本身:胶片条第 1 位一块永远删不掉的空白图,于是下一次上传只能排到第 2 位,规则继续什么都不画,预览也一直停在空帧。现在删除最后一张就是把列表清空。新建的规则也从空列表开始(第一张图到达时才分配槽位);手改导致「每一项都不可用」的列表同样落到这个状态,而不是被硬塞回一个坏槽位。只有「哪里都没提到可用图片」的规则(既没有列表、也没有旧式槽位)才会被 sanitizer 丢掉。
|
|
35
|
+
- **一张图都没有的规则,靠主题色继续生效** —— 空列表不等于「这条规则失效」,而第一版把它当成了失效:「可用」只看有没有图,于是一条图片删光、主题色还在的规则**不再参与匹配**,它那份主题色连同色轮、灵感色板、「跟随系统主题」全成了摆设(报告里的现象就是「主题色无效」),而它原本匹配的模型还会被悄悄交给**下一条**规则 —— 把兜底规则的图删光,屏幕上出现的就是另一个模型的壁纸。现在判定标准是**有图,或者有自己的主题色**(`ruleCanPaint`):这样的规则照旧匹配、照旧当兜底,只是不铺壁纸、只给界面配色;两者都没有(比如刚新增的空规则)才会被跳过。面板上的提示也按三种真实状态分开说:没有图但有主题色、既没有图也没有主题色、有图但选中的那张还没取到内容 —— 之前这三种情况共用一句「这条规则还没有图片」,其中两种是假话;「从本图提取」旁边那句「优先从本图取色」也不再出现在无图可取的时候。
|
|
36
|
+
- **凡是可能改变「谁赢」的编辑,现在都立刻生效;只有不可能改变的才允许跳过重画** —— `color`、`enabled`、`match` 都参与「哪条规则赢」的判定,但每条写入路径各自问的是一个很窄的问题(「这条规则是不是正在画的那条?」),于是只有对**已经**在生效的那条规则的编辑会重画。而「本来没在生效」的编辑恰恰就是**制造**赢家的那些:一条空规则的第一张图(正是它让这条规则变得可画 —— 见 `ruleCanPaint`),以及落在它身上的第一个颜色或自动提取出的颜色。这些改动确实进了配置、也进了面板自己的读数,但背后的界面还停在原来的规则上;报告里的说法是「更新的不太及时,只有我动调色盘或者切模型的时候才出现」—— 拖色轮或切模型才终于重跑了一次解析。现在每条路径都会在改动**之前**取一次赢家、改动之后再做比较(`repaintIfMoved`,判定本身是 `src/client/repaint.ts` 里的纯函数 `shouldRepaint`),这同时修好了一个会变旧的记录:当被编辑的规则**就是**赢家时,即使页面从没把它记成生效规则,也会重画。`pnpm check:repaint` 把这条判定的真值表走了一遍 —— 赢家移动的两个方向、以及那些必须保持廉价的情形 —— 并且只要入口文件里那九条写入路径(外加上传之后才到达的自动提取)中的任何一条不再询问它,就会失败。把它钉下来的实机验证让一条一次性规则走了完整序列(新建空规则 → 移到最上 → 上传一张图 → 之后什么都不碰):用旧判断时画面一直不动,直到有别的东西去动它;用现在这版,约 280 毫秒内画面就跟着换了,而且那张图自动提取出的颜色一起到位。
|
|
37
|
+
- **清空之后再上传** —— 你加进去的那张就是你正在看的那张(卡片会把编辑焦点移到新加入的第一张,胶片条、大预览与位置读数一起跟上),而不是把面板留在之前选中的东西上。
|
|
38
|
+
- **只加载用得到的图** —— 启动时只灌每条规则的**第一张**(与多图出现之前每条规则灌一张完全一致),外加今天生效的节日图与当前模型正在画的那张。其余的在展开卡片时、或轮换即将用到它时再取。若在启动时灌满每条规则的每张图,「五条规则各四张」就意味着首帧之前先把二十张全尺寸 data URL 从本插件自己的 RPC 通道推一遍;同理,store 报出的槽位列表也不再被灌进内存 —— 没有任何规则引用的孤儿槽位,本来就没有卡片可以显示它。
|
|
39
|
+
- **旧宿主进程再也不可能吃掉一份配置 —— 判定改成版本比较,格式版本因此升到 3** —— 浏览器半边与 node 半边是分开加载的,所以「更新包之后只刷新一次页面」会把**新的**客户端 bundle 与仍在运行**上一个** node 半边的宿主进程配在一起。那个旧 sanitizer 完全不认识 `images`,于是会在下一次拖动滑块时悄无声息地丢掉每一条规则。因此 node 半边会公布自己写入的配置格式(每次 `read` 都带 `schema`),而发现答案是旧版的客户端会**扣住所有配置写入**(唯一入口:`src/client/rpc.ts` 的 `canWriteConfig`)、在控制台警告一次,并在面板里说明「重启 DSH」。悄悄不保存的修改,比明说要等重启的修改更糟;而悄悄删掉规则的修改,比两者都糟。0.7.0 开发期间这道闸门真的漏过一次:当时版本号是 2,正在运行的 0.7 node 半边虽然也报 `schema: 2`,却是**空规则修复之前**的构建,于是「把规则删空」的写入照旧放行,宿主把这几条规则从磁盘上抹掉了。所以这个版本号现在表示的是「**空图片列表合法**」(`SCHEMA_VERSION = 3`),而不只是「规则里有 `images` 字段」:任何报出更低版本的宿主一律进入扣住写入的状态。
|
|
40
|
+
- **老配置照常读,老导出照常导入** —— `normalizeRule` 同时接受两种形状:规则里那对单图字段(`slot` / `bgState`)在读取时被提升成 `images: [{ slot, bgState }]`,同一条规则里重复的槽位被去掉,不可用的槽位被去掉。主题导出升到**格式版本 4**,导出与还原的是每条规则的**全部**图片而不只是第一张;版本 3 的文件仍可导入。
|
|
41
|
+
- **各图槽位互不干扰,删除规则只回收它自己那份** —— 一条三张图的规则占三个 `modelbg-<slot>` 文件;`nextSlot` 扫描配置里的**每一个**槽位(用户规则**与**节日条目都算),所以新加的一张图绝不会撞上已有的槽位;删除规则时,只有当某个槽位不再被任何地方引用时才回收。
|
|
42
|
+
- **node 半边连嵌套字段的漂移也一并报告,迁移直接写新形状** —— `warnUnknownConfigKeys` 现在会往深一层比较 `rules[].rotate` 与 `rules[].images[]` —— 一个只有客户端 bundle 知道的字段正是在那里无声消失的;同时 `slot` / `bgState` 这两个旧字段名被明确豁免,因为 sanitizer 是**故意**要提升它们的。0.3 之前配置的迁移也直接写出图片列表,而不是把一个旧形状留在磁盘上等第一次写入。
|
|
4
43
|
|
|
5
44
|
## v0.6.0
|
|
6
45
|
|
package/README.md
CHANGED
|
@@ -76,13 +76,28 @@ One interface, one config — only the current model differs:
|
|
|
76
76
|
|
|
77
77
|
- **Ordered Rule List** — Add, remove, reorder and name rules. Each rule is matched by its own match string; the first hit wins and rule 1 is the fallback.
|
|
78
78
|
- **Live Match Readout** — The top of the Model Background tab shows the current state, e.g. `Current model deepseek-flash → matched · Rule 1`, so you can verify a rule on the spot. If the current model can't be detected, a hint is shown instead.
|
|
79
|
-
- **Per-rule Appearance** — Every rule owns its
|
|
80
|
-
- **
|
|
79
|
+
- **Per-rule Appearance** — Every rule owns its wallpapers, layout mode, opacity and blur, and **every image owns its framing and its theme color**. Nothing is shared globally except the Interface tab.
|
|
80
|
+
- **Several Images per Rule** — A rule holds an ordered set of images, shown as a filmstrip: add several at once (file picker, drag & drop, or a list of URLs, one per line), reorder them, promote one to first, **replace one in place** (which keeps its position in the rotation), or remove them one by one. The image a rule paints when nothing rotates is its **first**. Removing the last image simply leaves the rule empty — and empty is not the same as off: while the rule keeps a theme color of its own it still matches, still serves as the fallback, and paints the interface instead of a wallpaper; only a rule with neither (a freshly added one) is skipped by matching. So an emptied rule is a visible, reversible state rather than one you get stuck in. While it holds no picture the filmstrip is hidden and the big preview tile **is** the upload button, so there is exactly one obvious place to add one.
|
|
81
|
+
- **Import / Export** — Export every rule **including all of its images** to a `dsh-background-by-model-theme.json` (format version 5, images inlined as base64) and restore it anywhere. Files written by earlier releases still import: each rule's single image is lifted into its image list, and a color written before per-image colors is lifted onto every image that has none — so an older theme file restores its look unchanged.
|
|
81
82
|
- **File-based Persistence** — All settings and images are stored on the filesystem under `~/.dsh/.dsh-background-by-model-data/`, not `localStorage`.
|
|
82
83
|
- **Automatic Migration** — Old single-wallpaper configs are upgraded in place on first read. See the [v0.3.0 entry in CHANGELOG.md](./CHANGELOG.md#v030).
|
|
83
84
|
- **Bilingual** — Full Chinese / English UI with automatic locale detection.
|
|
84
85
|
- **Theme Watchdog** — Re-asserts the custom theme if the host resets it.
|
|
85
86
|
|
|
87
|
+
### Image rotation
|
|
88
|
+
|
|
89
|
+
Per rule, and **off by default** — cycling spends real bandwidth, memory and battery, so nothing turns it on for you.
|
|
90
|
+
|
|
91
|
+
- **Enable it per rule** — the switch appears in the rule card and only becomes usable once the rule holds two or more images.
|
|
92
|
+
- **Dwell time** — 10 s / 30 s / 1 min / 5 min / 30 min / 1 h, or any custom value in seconds (clamped to 5 s – 24 h).
|
|
93
|
+
- **Order** — **In order** walks the list top-down and wraps around; **Shuffle** picks uniformly among every image *except* the one on screen, so a tick never looks like it was missed.
|
|
94
|
+
- **Also step on a model switch** — an optional second trigger: every time the model changes and lands on that rule, it steps once. That is the whole feature for a rule meant to show "a different picture every time" without any timer running.
|
|
95
|
+
- **Only the active rule rotates** — one timer for the whole plugin, aimed at whichever rule the current model resolved to. It stops while the tab is hidden and resumes on a fresh interval; a single-image rule (and every [holiday](#holiday-backgrounds)) never schedules anything.
|
|
96
|
+
- **Next image** — steps the wallpaper immediately, for when you want to check the set.
|
|
97
|
+
- **What follows a switch is the rule — except the color, which travels with the picture** — layout mode, opacity and blur belong to the rule and stay put. The theme color belongs to the **image**, so a rotation can move through a green picture, a red one and a system-themed one, and the interface palette changes with the wallpaper. The cross-fade between two images is the same one a model switch already used.
|
|
98
|
+
- **Framing is per image, and so is the color** — a crop and an accent both belong to a picture, so each image keeps its own while sharing the rule's layout, opacity and blur. The filmstrip marks the images that carry a color of their own, and the color controls always say which image they are editing.
|
|
99
|
+
- **Only the images it needs are loaded** — boot reads each rule's first image; the rest stream in when a card is expanded or when the rotation is about to need one. Ten images per rule therefore do not mean ten wallpapers transferred before the first frame.
|
|
100
|
+
|
|
86
101
|
### Holiday backgrounds
|
|
87
102
|
|
|
88
103
|
A small one, not a settings block: **one switch on the Config page and nothing else**. It is on out of the box, and on the day the background changes by itself and changes back the next morning.
|
|
@@ -93,15 +108,15 @@ A small one, not a settings block: **one switch on the Config page and nothing e
|
|
|
93
108
|
- **Mid-Autumn wins the overlap** — the two do collide (2025-10-06 was both); the single precise day is the better answer.
|
|
94
109
|
- **A holiday with no image falls through** to the model rules rather than blanking the wallpaper.
|
|
95
110
|
- **Built-in wallpapers** — Both holidays ship with a compressed wallpaper (228 KB and 500 KB at native resolution, against 8.37 MB of source art). Only the holiday that can paint *today* is ever fetched, so a profile that is not on a holiday transfers none of it.
|
|
96
|
-
- **Neither the art nor the palette is swappable, by design** — an easter egg that asks to be configured is not an easter egg. A holiday slot is read-only: a file dropped into `modelbg-h-midautumn` / `modelbg-h-nationalday` is ignored by every read, and the plugin refuses to write, fetch into or delete those slots. The theme colors are constants in the holiday definition and are forced on every read
|
|
111
|
+
- **Neither the art nor the palette is swappable, by design** — an easter egg that asks to be configured is not an easter egg. A holiday slot is read-only: a file dropped into `modelbg-h-midautumn` / `modelbg-h-nationalday` is ignored by every read, and the plugin refuses to write, fetch into or delete those slots. The theme colors are constants in the holiday definition and are forced on every read — on the holiday **and on its image**, since a theme color lives on the image now — so a hand-edited `color` in `theme-config.json` cannot repaint a festival either. Theme exports therefore carry your rules only.
|
|
97
112
|
- **Turning it off** — the switch on the Config page, or `"holidays": { "enabled": false }` in the same file.
|
|
98
113
|
|
|
99
114
|
### Per-rule settings
|
|
100
115
|
|
|
101
|
-
- **Wallpaper** —
|
|
102
|
-
- **Theme Color** — HSL wheel plus numeric input and an inspiration palette, with **Extract from this image** and an **eyedropper**.
|
|
116
|
+
- **Wallpaper** — Give a rule one or more images; each image is its own file. The filmstrip picks which one you are editing, and the "first" one is what the rule paints when no rotation is running. A rule may also hold **no** image: it then paints no wallpaper, but as long as it has a theme color of its own it still matches and still serves as the fallback (painting the interface alone), and only with neither is it skipped by matching until you give it something.
|
|
117
|
+
- **Theme Color** — HSL wheel plus numeric input and an inspiration palette, with **Extract from this image** and an **eyedropper**. **The color belongs to the image**: those controls edit the one selected in the filmstrip, the strip shows which images already carry a color of their own, and an image that has none follows the system theme rather than inheriting its rule's. A rule with **no image at all** has only its own color left to edit — and that is exactly what such a rule paints. Generates the full CSS design-token set in real time. Following the system theme hands the palette back to the host while the wallpaper and the Interface-tab opacity/blur sliders keep working — the surface colors are read back from the host's own tokens and re-emitted with your alpha, so the wallpaper is never buried under an opaque plate.
|
|
103
118
|
- **Layout Mode** — Fit / Fill / Stretch / Tile / Center.
|
|
104
|
-
- **Framing** — Drag to pan and scroll to zoom inside a viewport-proportional editor; **only editable in Fit mode**, and the committed framing stays consistent across window resizes and cross-monitor moves.
|
|
119
|
+
- **Framing** — Drag to pan and scroll to zoom inside a viewport-proportional editor; **only editable in Fit mode**, and the committed framing stays consistent across window resizes and cross-monitor moves. Stored per image.
|
|
105
120
|
- **Background Opacity** — `0–100%` for the rule's wallpaper layer.
|
|
106
121
|
- **Background Blur** — `0–60 px`, applied to the wallpaper layer.
|
|
107
122
|
|
|
@@ -119,7 +134,7 @@ Settings → **Theme** now has three tabs:
|
|
|
119
134
|
| Tab | Scope | Contents |
|
|
120
135
|
| --- | --- | --- |
|
|
121
136
|
| **Interface** | Global, shared by every model | Opacity & blur for the main background, left panel, right panel, cards & panels, input & controls, settings panel, conversation text box and trajectory page |
|
|
122
|
-
| **Model Background** | Per rule | The ordered rule list, the live match readout, and each rule's
|
|
137
|
+
| **Model Background** | Per rule | The ordered rule list, the live match readout, and each rule's images (filmstrip), theme color, layout mode, per-image framing, opacity, blur and [rotation](#image-rotation) |
|
|
123
138
|
| **Config** | — | Import / export the whole rule set (`dsh-background-by-model-theme.json`), plus the single [holiday background](#holiday-backgrounds) switch |
|
|
124
139
|
|
|
125
140
|
The old **Color** tab is gone — the theme color is now a property of each rule. The old **Background** tab became **Model Background**.
|
|
@@ -130,8 +145,8 @@ Data directory: `~/.dsh/.dsh-background-by-model-data/` (Windows: `C:\Users\<you
|
|
|
130
145
|
|
|
131
146
|
| File | Contents |
|
|
132
147
|
| --- | --- |
|
|
133
|
-
| `theme-config.json` | The rule list plus the global Interface settings and the holiday overrides |
|
|
134
|
-
| `modelbg-<slot>` |
|
|
148
|
+
| `theme-config.json` | The rule list (each rule with its image list and rotation) plus the global Interface settings and the holiday overrides |
|
|
149
|
+
| `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 |
|
|
135
150
|
| `modelbg-h-midautumn` / `modelbg-h-nationalday` | Not used: both holiday wallpapers ship inside the package and are served straight out of it |
|
|
136
151
|
|
|
137
152
|
## Installation
|
|
@@ -184,11 +199,13 @@ To mount a working copy into a profile instead, add it to the profile's `package
|
|
|
184
199
|
|
|
185
200
|
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.
|
|
186
201
|
|
|
187
|
-
`pnpm test` builds, typechecks and runs
|
|
202
|
+
`pnpm test` builds, typechecks and runs five checks (`tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/repaint-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs`). None of them needs a dependency, a transformer or a browser, and each can be run alone with `pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:repaint` / `pnpm check:ui` / `pnpm check:node`:
|
|
188
203
|
|
|
189
204
|
- **`check:holiday`** — the two holiday windows against known dates, the Beijing day boundary (23:59 vs 00:01), leap eighth months across 1900–2100, and the four gates `pickHoliday` applies before a holiday is allowed to take over the background.
|
|
205
|
+
- **`check:rotation`** — the rotation's decisions (`nextIndex` / `isRotating`): no walking off the end of a list, shuffle never repeating the image already on screen (across the whole support of the roll), and "rotating" never claimed for a rule with nothing to rotate.
|
|
206
|
+
- **`check:repaint`** — the decision that says whether an edit has to repaint the interface (`shouldRepaint`), including the case that kept failing in the field: an edit that **creates** the winner (a rule's first picture, its first or auto-extracted color) must repaint even though the edited rule was not the active one yet. It also fails if any of the write paths in `src/client/index.tsx` stops asking — so re-introducing the old `id === activeRuleId` test breaks the build's checks instead of the user's next upload — and if a rotation step stops re-emitting the interface palette (a theme color belongs to the image, so a step changes it).
|
|
190
207
|
- **`check:ui`** — every `t('…')` key exists in both dictionaries, both dictionaries carry the same key set, every `dab-…` class has a rule in the stylesheet, and no dictionary entry has gone unreferenced.
|
|
191
|
-
- **`check:node`** — the built node half driven through its real RPC handler with `DSH_HOME` redirected to a throwaway directory: the holiday block through the shared sanitizer, the packaged wallpapers served from an empty slot (without being copied into the data directory), a holiday slot refusing writes, deletes and URL fetches while a file dropped into it is ignored, an ordinary rule slot still writing, listing and deleting,
|
|
208
|
+
- **`check:node`** — the built node half driven through its real RPC handler with `DSH_HOME` redirected to a throwaway directory: the holiday block through the shared sanitizer, the packaged wallpapers served from an empty slot (without being copied into the data directory), a holiday slot refusing writes, deletes and URL fetches while a file dropped into it is ignored, an ordinary rule slot still writing, listing and deleting, a hand-edited config unable to redirect a holiday at another rule's image, and the per-image color shape: the read-time lift in both directions (an absent key inherits the rule's color, an explicit `null` does not), independent colors through the file, and a `null` written as a key rather than omitted.
|
|
192
209
|
|
|
193
210
|
## FAQ
|
|
194
211
|
|
|
@@ -199,7 +216,25 @@ Your first rule probably has an empty match string, which makes it a pure fallba
|
|
|
199
216
|
Matching is first-hit, not best-hit. Put the more specific rule higher in the list, or reorder so the rule you want comes first.
|
|
200
217
|
|
|
201
218
|
**Where are my wallpapers?**
|
|
202
|
-
In `~/.dsh/.dsh-background-by-model-data/`, one `modelbg-<slot>` file per
|
|
219
|
+
In `~/.dsh/.dsh-background-by-model-data/`, one `modelbg-<slot>` file per image plus `theme-config.json`.
|
|
220
|
+
|
|
221
|
+
**I turned the rotation on and the background never changed.**
|
|
222
|
+
Three things can hold it: the rule needs two or more images, the tab has to be visible (a hidden tab paints nothing, so the rotation pauses with it), and only the rule the current model resolved to rotates — a rule that is not active has a disabled **Next image** button for exactly that reason.
|
|
223
|
+
|
|
224
|
+
**Does rotating cost anything?**
|
|
225
|
+
Yes, and that is why it is off by default. Each image is stored at full fidelity — no re-encoding — so a rule with ten 8 MB wallpapers holds ten of them, and every switch repaints the whole interface. Keep the sets small (two to five images is the sweet spot) and the dwell time sane; boot only reads each rule's first image, and the rotation warms the image it is about to need, so the cost is memory and disk rather than start-up time.
|
|
226
|
+
|
|
227
|
+
**Why did the theme color stop working after I removed a rule's images?**
|
|
228
|
+
That was the old behaviour, and its root cause was reading "has an image" as "is usable": a rule with no pictures left stopped matching at all, so its color had nothing to act on — and the models it used to own were handed to the next rule instead. A rule now counts while it has **an image, or a theme color of its own**: no wallpaper, the interface painted from that color (the card says which state it is in). Only a rule with neither — nothing to show at all — is skipped, with the card spelling that out.
|
|
229
|
+
|
|
230
|
+
**I updated the plugin and the panel says my edits are not being saved.**
|
|
231
|
+
The browser half and the host half are updated separately: a refreshed page can run the new client while DSH's own process still runs the previous plugin, which cannot read the new config shape (it announces a lower `SCHEMA_VERSION`) and could drop rules on the next write. The client detects that and holds its writes — restart DSH and the multi-image config, rotation settings and everything else save normally.
|
|
232
|
+
|
|
233
|
+
**I set a color for one image and the other images did not change.**
|
|
234
|
+
That is the design: since 0.7.1 a theme color belongs to the **image**, not to the rule, so a rotation can move through pictures with different accents (a green one, a red one, and one that follows the system theme). The color controls edit whichever image the filmstrip has selected, and the hint above them names it. A rule with **no image at all** is the one case where the controls edit the rule's own color — that color is what such a rule paints, and it is deliberately not a default for the rule's pictures.
|
|
235
|
+
|
|
236
|
+
**Where did the theme color I set before this update go?**
|
|
237
|
+
Nowhere: an image written before per-image colors has no color of its own at all, which is exactly what the loader uses to tell "never had one" from "cleared on purpose", and it lifts the rule's color onto every such image. So a themed rule comes back looking identical, and from then on each picture can be retinted on its own.
|
|
203
238
|
|
|
204
239
|
**Can I share a setup?**
|
|
205
240
|
Yes — export it from the **Config** tab; the JSON inlines every rule's image.
|
|
@@ -218,19 +253,26 @@ The other half of the same hole: while it owns a color the plugin **forces** `bo
|
|
|
218
253
|
|
|
219
254
|
## Recent Optimizations
|
|
220
255
|
|
|
221
|
-
> Only the two most recent releases are listed here; older ones (v0.
|
|
256
|
+
> Only the two most recent releases are listed here; older ones (v0.7.0 and earlier) live in [CHANGELOG.md](./CHANGELOG.md).
|
|
222
257
|
|
|
223
|
-
### v0.
|
|
258
|
+
### v0.7.2
|
|
224
259
|
|
|
225
|
-
- **
|
|
226
|
-
-
|
|
227
|
-
- **The
|
|
228
|
-
- **
|
|
260
|
+
- **The filmstrip IS the control: drag to reorder, double-click to show** — the order used to be reachable only through a pair of ↑ / ↓ arrows and a "make first" button in the row below, when the five thumbnails already show the order they are in. Grab any tile and drop it where it belongs (the drop point is a 2px bar), and give "which picture is on screen" to the strip's other gesture: a double-click, which changes no order and **writes nothing at all** — the image being shown is runtime state, not a setting, so the next boot starts at the rule's first image again.
|
|
261
|
+
- **The order and "which one is on screen" no longer interfere (a fix)** — both old actions ended by moving the image index onto the image they had just moved, so tidying the list swapped the wallpaper with it (most visible with the rotation off, where the index is sticky: "even with the rotation off, the background can change when I adjust the order"). `moveRuleImageTo` replaces both: it remembers the SLOT being painted before the splice and puts the index back on it afterwards. The three buttons left with the two uncalled actions behind them.
|
|
262
|
+
- **The tile on screen keeps its dot and loses its ring** — the tile open for editing already has `.is-sel`'s ring, and a second one put two "selected" tiles in a row of near-identical thumbnails; being on screen is the dot, and nothing else.
|
|
263
|
+
- **Every image of a batch upload gets its own color** — the extraction was addressed by POSITION (it only asked about `rule.images[length - 1]`), which is right for a one-file upload and silently wrong for a batch: the pictures before the last kept no color of their own and stayed on the system theme. It is now addressed by the slots the batch itself just appended, so which pictures get themed depends on neither where they landed nor which one the card selects.
|
|
264
|
+
- **Copy trimmed** — the "Rules" heading, its priority sentence and the automatic-extraction sentence are gone (the panel's title and the `fallback` badge already say both), `Add images` / `Replace this image` / `Remove image` lose the noun they were repeating, and `Edit position` moved to the end of the layout row (it edits how the picture fills the area, and still appears in **fit** only).
|
|
229
265
|
|
|
230
|
-
### v0.
|
|
266
|
+
### v0.7.1
|
|
231
267
|
|
|
232
|
-
- **The color
|
|
233
|
-
- **
|
|
268
|
+
- **The theme color moved down to the image** — a rule is a rotation through several pictures, and pictures collected for different moods do not share one accent. Each image now carries its own `color`, and the wheel, the numeric inputs, the inspiration palette, **Extract from this image**, the eyedropper and **Follow system theme** all edit the image selected in the filmstrip. The controls say which image they are addressing and the strip marks the images that already have a color of their own, so "I changed it and nothing happened" cannot be the first thing this feature teaches.
|
|
269
|
+
- **A rule's own color now means exactly one thing** — what a rule with **no image left** paints. It is not a default for the rule's pictures: an image with no color of its own follows the **system theme** rather than inheriting it, which is what makes a rotation through a green picture, a red one and a system-themed one expressible at all. Clearing one image's color therefore clears only that image, and the rule's color survives for the state where it is the only thing left to paint.
|
|
270
|
+
- **Nothing loses its theme on upgrade** — an image entry written before this release has no `color` key at all, and that is the whole distinction the loader uses: a missing key means "never had a color of its own", so the rule's color is lifted onto it (once, in the shared sanitizer, so both halves lift identically); an explicit `null` means "cleared on purpose" and is left alone. The rule keeps its color as well, so emptying a themed rule still paints it.
|
|
271
|
+
- **A rotation step re-emits the palette, and that is not a detail** — the step only repainted the wallpaper, so a per-image color would have appeared when *something else* re-ran an apply: dragging the color wheel, or switching models. That is the exact symptom this plugin already had once ("it only updates when I touch the color wheel"), so the palette apply is now one function called by both a model switch and every step of the rotation (the timer and the manual **Next image** button, which share one funnel).
|
|
272
|
+
- **Holidays carry their palette per image too** — a festival's fixed color is written on the holiday *and* on its image and is forced from the definition on every read, so the shape is ready for a holiday that rotates through several pieces of art, each with its own palette, as a change to `HOLIDAYS` and its assets rather than a second code path in the render layer.
|
|
273
|
+
- **A rule with no picture has exactly one upload target** — the big empty preview tile *is* the button now (click it, or drop files onto it), and the filmstrip is not rendered at all while the rule holds nothing: it contained nothing but a second "add image" tile, and a filmstrip is a list of pictures — a state with none does not have one. Nothing else moved: the labelled **Add images** / **From URL** buttons stay where they were, and with pictures present the preview is still deliberately inert (dropping on the strip adds, and *replace* stays an explicit labelled action, which is what keeps a preview from swallowing a click the user did not mean).
|
|
274
|
+
- **The config shape moved to 4, and an older host still cannot eat a profile** — `images[].color` is unknown to a schema-3 sanitizer, which rebuilds each image entry from the keys it knows: the color would survive in memory until the next reload and then be gone, with nothing in the log to explain it. The version check is a plain `>=`, so a host announcing less holds every write and says so in the panel until DSH is restarted.
|
|
275
|
+
- **The gates cover it** — `check:node` now pins the lift in both directions (an absent key inherits the rule's color, an explicit `null` does not), per-image independence through the file, holidays forcing both colors, and that a `null` is written as an explicit key rather than as an absent one (writing it as absent would silently re-inherit the rule's color on every load). `check:repaint` additionally fails if a rotation step ever stops re-emitting the palette.
|
|
234
276
|
|
|
235
277
|
## Star History
|
|
236
278
|
|
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
|
|
|
@@ -76,32 +76,47 @@
|
|
|
76
76
|
|
|
77
77
|
- **有序规则列表** — 可新增、删除、拖动排序并命名规则;每条规则用自己的匹配串参与匹配,先命中者生效,规则 1 兼任兜底。
|
|
78
78
|
- **实时状态提示** — 「模型背景」页顶部会显示当前状态,例如「当前模型 deepseek-flash → 命中 · 规则 1」,方便当场验证规则写对没有;检测不到当前模型时会给出提示。
|
|
79
|
-
-
|
|
80
|
-
-
|
|
79
|
+
- **外观随规则走,颜色随图走** — 壁纸(多张)、布局模式、透明度与模糊度是每条规则自己的属性;**取景和主题色则是每张图片自己的**。除「界面」页外没有任何全局外观项。
|
|
80
|
+
- **一条规则多张图** — 每条规则持有一组有序图片,用胶片条展示:可一次加多张(多选文件、拖放,或一行一个网址批量粘贴)、排序、把某张设为第一张、**原地替换某一张**(替换会保住它在轮换里的位置)、逐张删除。不轮换时规则画的就是**第一张**。删掉最后一张只是让这条规则变空,而空不等于失效:只要它还留着自己的主题色,它就照旧参与匹配与兜底、只给界面配色而不铺壁纸;连主题色也没有(比如刚新增的规则)才会被匹配跳过。空规则是一个看得见、退得回的状态,而不是卡住出不来的死胡同。一张图都没有时胶片条会隐藏、上方那块大预览**就是**上传按钮,所以添加图片的地方只有一个、也很显眼。
|
|
81
|
+
- **导入 / 导出** — 一键把**每一条规则连同它的全部图片**导出为 `dsh-background-by-model-theme.json`(版本 5,图片以 base64 内联),可随时导入还原。更早版本导出的文件同样能导入:规则唯一的那张图会被还原成它的图片列表,而在「主题色属于图片」之前写下的颜色会被抬升到每一张还没有自己颜色的图上——所以旧主题文件还原出来的观感与当初一模一样。
|
|
81
82
|
- **文件持久化** — 所有设置与图片保存到文件系统 `~/.dsh/.dsh-background-by-model-data/`,不依赖 `localStorage`。
|
|
82
83
|
- **自动迁移** — 旧版单壁纸配置在首次读取时自动升级,详见 [CHANGELOG.zh.md 的 v0.3.0 条目](./CHANGELOG.zh.md#v030)。
|
|
83
84
|
- **中英双语** — 完整的中英文界面,自动跟随语言设置。
|
|
84
85
|
- **主题守护** — 宿主重置主题后自动重新激活自定义主题。
|
|
85
86
|
|
|
87
|
+
### 多图轮换
|
|
88
|
+
|
|
89
|
+
按规则开关,且**默认关闭** —— 轮换是真要花流量、内存和电的,不该由默认值替你决定。
|
|
90
|
+
|
|
91
|
+
- **按规则开启** — 开关就在规则卡片里,规则至少有两张图之后才能打开。
|
|
92
|
+
- **每张停留** — 10 秒 / 30 秒 / 1 分钟 / 5 分钟 / 30 分钟 / 1 小时,也可以自己填秒数(限制在 5 秒 – 24 小时)。
|
|
93
|
+
- **换图顺序** — 「按顺序」自上而下走完一圈再回到第一张;「随机」会在**除当前这张之外**的所有图里等概率挑一张,因为抽到同一张看起来就跟这一次没生效一模一样。
|
|
94
|
+
- **切换模型时也换一张** — 可选的第二种触发方式:每次切换模型落到这条规则上就往前走一张。对「每次都想看到不一样」的规则来说,这就是全部功能,而且不用挂任何定时器。
|
|
95
|
+
- **只有正在生效的规则会轮换** — 全插件只有一个定时器,始终对准当前模型解析出的那条规则。标签页不可见时它会停下、回来时重新计时;只有一张图的规则(以及所有[节日](#节日特殊背景))从不排期。
|
|
96
|
+
- **下一张** — 立刻换一张,方便当场检查这组图。
|
|
97
|
+
- **跟着规则走的是外观,跟着图片走的是颜色** — 布局模式、透明度、模糊度留在规则上、原地不动;**主题色属于图片**,所以一组图可以依次是绿的、红的、以及跟随系统主题的,界面配色跟着壁纸一起换。过渡用的就是切换模型时那套交叉淡入。
|
|
98
|
+
- **取景是逐图的,颜色也是** — 构图和配色都属于某一张图:每张图各自保留自己的取景与主题色,同时共享规则的布局、透明度与模糊度。胶片条会标出哪些图已经各自有颜色,调色区上方也会写明当前编辑的是哪一张。
|
|
99
|
+
- **只加载用得到的图** — 启动时只读每条规则的**第一张**,其余的在展开卡片时、或者在轮换即将用到它时再读。所以「五条规则各十张图」不等于首帧之前先传五十张壁纸。
|
|
100
|
+
|
|
86
101
|
### 节日特殊背景
|
|
87
102
|
|
|
88
103
|
小小一个,不是一整块设置:**「配置」页上一个开关,别的什么都没有**。它默认就是开的;到了日子背景自己换,第二天早上自己换回。
|
|
89
104
|
|
|
90
|
-
- **行为** — 中秋与国庆当天,壁纸换成那个节日自己的图片,并配上该节日固定的主题色(中秋 `#384A77`、国庆 `#FFF6EB
|
|
105
|
+
- **行为** — 中秋与国庆当天,壁纸换成那个节日自己的图片,并配上该节日固定的主题色(中秋 `#384A77`、国庆 `#FFF6EB`);第二天交还给模型规则。这份配色同时写在节日条目和它的图片上——主题色现在住在图片里——所以以后节日也能多图轮换、每张带自己的配色。
|
|
91
106
|
- **中秋节** — 农历八月十五当天。它在公历上逐年移动(2024-09-17、2025-10-06、2026-09-25、2027-09-15……),所以是运行时按农历换算的,时区钉在 **Asia/Shanghai** —— 农历日期在北京时间午夜翻页,不是你那边的午夜。
|
|
92
107
|
- **国庆节** — 公历 10 月 1–7 日。
|
|
93
108
|
- **两者重叠时中秋优先** — 它们确实会撞(2025-10-06 就是同一天),一个精确的单日比七天窗口里的某一天更值得采信。
|
|
94
109
|
- **没有图的节日会回落到模型规则**,而不是把背景刷空。
|
|
95
110
|
- **内置壁纸** — 两个节日都自带一张压缩过的壁纸(原生分辨率下 228 KB 与 500 KB,原图共 8.37 MB)。只会去取**今天真能生效**的那一张,所以不在节日的配置一点都不会传。
|
|
96
|
-
- **图片和配色都不可替换,这是刻意的** — 一个要求用户去配置的彩蛋就不是彩蛋了。节日槽位是只读的:往 `modelbg-h-midautumn` / `modelbg-h-nationalday`
|
|
111
|
+
- **图片和配色都不可替换,这是刻意的** — 一个要求用户去配置的彩蛋就不是彩蛋了。节日槽位是只读的:往 `modelbg-h-midautumn` / `modelbg-h-nationalday` 里放文件不会被任何一次读取采用,插件也拒绝写入、抓取和删除这两个槽位。主题色是节日定义里的常量、每次读取都强制覆盖——**节日条目和它的图片两处都覆盖**,所以手改 `theme-config.json` 里的 `color` 也换不掉节日配色。导出的主题文件只包含你自己的规则。
|
|
97
112
|
- **不想要它** — 关掉「配置」页那个开关,或者在同一个文件里写 `"holidays": { "enabled": false }`。
|
|
98
113
|
|
|
99
114
|
### 每条规则自带的外观
|
|
100
115
|
|
|
101
|
-
- **背景图片** —
|
|
102
|
-
- **主题色** — HSL 色轮 + 数值输入 +
|
|
116
|
+
- **背景图片** — 一条规则可以放一张或多张图,每张图是独立文件。胶片条决定你正在编辑哪一张;不轮换时规则画的是**第一张**。规则也可以是**一张图都没有**的:这时它不再铺壁纸,但只要它有自己的主题色就照旧参与匹配与兜底(只是把界面按那个颜色配色),连主题色也没有才会被匹配跳过,直到你给它一点内容。
|
|
117
|
+
- **主题色** — HSL 色轮 + 数值输入 + 灵感色板,外加「从本图提取」和吸管「从图片取色」。**颜色属于图片**:这些控件编辑的是胶片条里选中的那张,胶片条上会标出哪些图已经各自有颜色,而没有自己颜色的图**跟随系统主题**、不会去继承规则的颜色。**一张图都没有**的规则只剩它自己的主题色可编辑——那正是这种规则唯一能画的东西。实时生成整套 CSS 设计令牌。跟随系统主题时,界面配色交还宿主,而壁纸与「界面」页的透明度、模糊照旧生效——表面颜色是从宿主自己的令牌读出来后套上你的透明度的,所以壁纸不会被一块不透明底板盖住。
|
|
103
118
|
- **布局模式** — 适应 / 填充 / 拉伸 / 平铺 / 居中。
|
|
104
|
-
- **取景** —
|
|
119
|
+
- **取景** — 在视口比例的编辑器中拖动平移、滚轮缩放;**仅「适应」模式下可编辑**,提交的构图在窗口缩放、跨屏移动后保持一致。按图分别保存。
|
|
105
120
|
- **背景透明度** — `0–100%`,作用于该规则的壁纸层。
|
|
106
121
|
- **背景模糊** — `0–60 px`,作用于壁纸层。
|
|
107
122
|
|
|
@@ -119,7 +134,7 @@
|
|
|
119
134
|
| 选项卡 | 作用范围 | 内容 |
|
|
120
135
|
| --- | --- | --- |
|
|
121
136
|
| **界面** | 全局,所有模型共用 | 主背景、左侧栏、右侧栏、卡片面板、输入框与控件、设置面板、对话文本框、轨迹页的透明度与模糊度 |
|
|
122
|
-
| **模型背景** | 每条规则 |
|
|
137
|
+
| **模型背景** | 每条规则 | 有序规则列表、顶部实时状态,以及每条规则的图片(胶片条)、**逐图主题色**、布局模式、逐图取景、透明度、模糊度与[多图轮换](#多图轮换) |
|
|
123
138
|
| **配置** | — | 导入 / 导出整套规则(`dsh-background-by-model-theme.json`),外加[节日特殊背景](#节日特殊背景)那一个开关 |
|
|
124
139
|
|
|
125
140
|
原先的 **「色彩」** 选项卡已删除——主题色改为每条规则自己的属性;原 **「背景」** 选项卡已改造成 **「模型背景」**。
|
|
@@ -130,8 +145,8 @@
|
|
|
130
145
|
|
|
131
146
|
| 文件 | 内容 |
|
|
132
147
|
| --- | --- |
|
|
133
|
-
| `theme-config.json` |
|
|
134
|
-
| `modelbg-<slot>` |
|
|
148
|
+
| `theme-config.json` | 规则列表(每条规则含图片列表与轮换设置)+ 全局「界面」设置 + 节日覆盖配置 |
|
|
149
|
+
| `modelbg-<slot>` | 一张图片,保存为原始字节,无扩展名 —— 一张图一个文件,所以一条有三张图的规则占三个槽位 |
|
|
135
150
|
| `modelbg-h-midautumn` / `modelbg-h-nationalday` | 不使用:两张节日壁纸都随包发布,直接从包内送出 |
|
|
136
151
|
|
|
137
152
|
## 安装
|
|
@@ -184,11 +199,13 @@ pnpm run typecheck
|
|
|
184
199
|
|
|
185
200
|
改完 `src/` 必须重新执行 `pnpm run bundle` 才会生效——挂载的 profile 加载的是 `lib/`。
|
|
186
201
|
|
|
187
|
-
`pnpm test`
|
|
202
|
+
`pnpm test` 会构建、类型检查并跑五道校验(`tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/repaint-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs`)。五道都不需要依赖、不需要转译器、也不需要浏览器,也可以单独跑:`pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:repaint` / `pnpm check:ui` / `pnpm check:node`。
|
|
188
203
|
|
|
189
204
|
- **`check:holiday`** — 两个节日窗口对照已知日期、北京时间的跨日边界(23:59 与 00:01)、1900–2100 年间的闰八月,以及 `pickHoliday` 允许节日接管背景之前必须通过的四道闸门。
|
|
205
|
+
- **`check:rotation`** — 轮换的判定(`nextIndex` / `isRotating`):不会走越界、随机不会重复当前这张(在整个随机取值范围内穷举)、也不会把只有一张图的规则报成「正在轮换」。
|
|
206
|
+
- **`check:repaint`** — 「这次编辑要不要重画界面」的判定(`shouldRepaint`),其中就包含实际踩过的那个坑:**这次编辑本身**制造出了生效规则(某条规则的第一张图、第一个颜色或自动提取出的颜色)时,即使被编辑的规则此刻还不是生效规则,也必须立刻重画。它同时还会在 `src/client/index.tsx` 里任何一条写入路径不再询问这个判定时报错——也就是说,把老的 `id === activeRuleId` 判断写回去,会是构建校验失败,而不是你下一次上传时界面不动;轮换步骤不再重发调色板(主题色属于图片,换图就是换配色)同样会被它拦下。
|
|
190
207
|
- **`check:ui`** — 每个 `t('…')` 键在中英两本字典里都存在、两本字典键集完全一致、每个 `dab-…` 类在样式表里都有规则,且没有字典条目变成没人引用的死键。
|
|
191
|
-
- **`check:node`** — 把构建好的 node 半边通过它真实的 RPC 处理器跑一遍,`DSH_HOME` 指向临时目录:节日配置块穿过共享的 sanitizer、内置壁纸从空槽位被送出(且不会因此写进数据目录)、节日槽位拒绝写入 / 删除 /
|
|
208
|
+
- **`check:node`** — 把构建好的 node 半边通过它真实的 RPC 处理器跑一遍,`DSH_HOME` 指向临时目录:节日配置块穿过共享的 sanitizer、内置壁纸从空槽位被送出(且不会因此写进数据目录)、节日槽位拒绝写入 / 删除 / 抓取而丢进去的文件被忽略、普通规则槽位照常写入和删除,以及手改过的配置无法把节日指向别的规则的图片。它还覆盖逐图主题色这套形状:读取时抬升的两个方向(缺键继承规则色、显式 `null` 不继承)、颜色经过文件往返后各自独立,以及 `null` 是写出键而不是省略。
|
|
192
209
|
|
|
193
210
|
## 常见问题
|
|
194
211
|
|
|
@@ -199,7 +216,25 @@ pnpm run typecheck
|
|
|
199
216
|
匹配是先命中者胜,而不是最精确者胜。把更具体的规则往上拖,或调整顺序让你想要的规则排在前面。
|
|
200
217
|
|
|
201
218
|
**图片存在哪?**
|
|
202
|
-
在 `~/.dsh/.dsh-background-by-model-data
|
|
219
|
+
在 `~/.dsh/.dsh-background-by-model-data/`:每张图一个 `modelbg-<slot>` 文件,外加 `theme-config.json`。
|
|
220
|
+
|
|
221
|
+
**我开了轮换,背景却一直没变。**
|
|
222
|
+
有三件事会拦住它:规则至少要有两张图;标签页必须可见(隐藏的标签页什么都不画,轮换跟着一起停);只有当前模型解析出的那条规则在轮换——没生效的规则,「下一张」按钮就是灰的,正是这个原因。
|
|
223
|
+
|
|
224
|
+
**轮换有代价吗?**
|
|
225
|
+
有,所以默认关闭。每张图都按原始保真度保存(不做重编码),所以一条放了十张 8 MB 壁纸的规则就是十份 8 MB;每次换图还会重画整个界面。建议一组控制在两到五张、节奏也别太密。启动时只读每条规则的第一张,轮换也会提前预热它下一张要用的图,所以代价落在内存和磁盘上,而不是启动时间上。
|
|
226
|
+
|
|
227
|
+
**删掉规则里的图片之后,主题色为什么不起作用了?**
|
|
228
|
+
这是已经修掉的旧行为,根因是「有没有图」被当成了「这条规则是否可用」:图片删光的规则不再参与匹配,效果自然也就无从谈起,而它原本负责的模型还会被交给下一条规则。现在只要规则**有图、或者有自己的主题色**就仍然生效:不铺壁纸,只按这个颜色给界面配色(面板上会写明当前属于哪种状态)。连主题色也没有、也就是一点内容都没有的规则才会被跳过——「这条规则还没有图片,也没有自己的主题色:没有可显示的内容,匹配和兜底都会跳过它」。
|
|
229
|
+
|
|
230
|
+
**我更新了插件,面板说我的改动没有保存。**
|
|
231
|
+
浏览器半边和宿主半边是分开加载的:刷新页面后新客户端可能已经生效,而 DSH 主进程还在跑上一个版本的插件——它读不懂新的配置格式(`SCHEMA_VERSION` 比它低就知道),下一次写入就可能把规则丢掉。客户端检测到这种情况会**扣住所有配置写入**并提示你:重启 DSH 之后,多图配置、轮换设置等一切都会正常保存。
|
|
232
|
+
|
|
233
|
+
**我给一张图设了颜色,其它图片却没跟着变。**
|
|
234
|
+
这是设计如此:从 0.7.1 起主题色属于**图片**而不是规则,所以一组图可以依次是不同调性(绿的、红的、以及跟随系统主题的)。调色控件作用于胶片条里选中的那一张,控件上方的提示会写明是哪一张。唯一例外是**一张图都没有**的规则:那时控件编辑的是规则自己的主题色——那正是这种规则能画的东西,而且它刻意不作为该规则图片的默认值。
|
|
235
|
+
|
|
236
|
+
**更新之前我设的主题色去哪了?**
|
|
237
|
+
哪儿也没去:在「逐图主题色」之前写下的图片条目**根本没有** `color` 这个键,而这正是加载时用来区分「从来没有自己的颜色」和「被主动清空过」的依据——前者会把规则的颜色抬升到每一张这样的图上。所以带主题色的规则升级后观感完全一致,此后每张图就可以各自改色了。
|
|
203
238
|
|
|
204
239
|
**能分享整套配置吗?**
|
|
205
240
|
可以,在 **「配置」** 页导出即可;导出的 JSON 会把每条规则的图片内联进去。
|
|
@@ -218,19 +253,26 @@ pnpm run typecheck
|
|
|
218
253
|
|
|
219
254
|
## 近期优化
|
|
220
255
|
|
|
221
|
-
> 这里只列最近两个版本;更早的版本(v0.
|
|
256
|
+
> 这里只列最近两个版本;更早的版本(v0.7.0 及以前)见 [CHANGELOG.zh.md](./CHANGELOG.zh.md)。
|
|
222
257
|
|
|
223
|
-
### v0.
|
|
258
|
+
### v0.7.2
|
|
224
259
|
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
260
|
+
- **胶片条自己就是控件:拖拽排顺序,双击换画面** — 顺序以前只能靠下面那排 ↑ / ↓ 和「设为第一张」改,而五个缩略图本来就把顺序摆在那里。现在抓住任意一张拖到新位置即可,落点是一根 2px 竖条;而「哪张在屏上」交给了双击 —— 它不改顺序,也**不写任何配置**:在屏哪张是运行时状态而不是设置,所以下次启动照旧从第一张开始。
|
|
261
|
+
- **顺序和在屏的是哪张,从此互不干涉(修复)** — 旧的两个动作都以「把下标挪到刚被移动的那张图上」结尾,于是整理列表会把壁纸一起换掉(轮换关着、下标粘住时最容易撞见:「我调整顺序时,即便关了轮换,当前背景也可能会变」)。新的 `moveRuleImageTo` 取代了它们:摘出之前先记下**正在画的槽位**,插回去后把下标放回它身上;三个按钮和背后两个不再被调用的动作一起删掉。
|
|
262
|
+
- **在屏那张只留圆点,不再有边框** — 「正在编辑」已经有 `.is-sel` 的圈了,再给「正在显示」画一个,一排几乎一样的缩略图里就同时有两个「选中」;这是在屏状态唯一不能留给人猜的地方。
|
|
263
|
+
- **一次上传的多张图,每张都会各自取色** — 取色原本是**按下标**寻址的(只问 `rule.images[length - 1]`),对单张上传正确、对批量静默地错:最后一张之前的图没有自己的颜色、留在系统主题上。现在按**这一批自己刚追加的槽位**寻址,与它们落在哪个位置、卡片选中了哪张都无关。
|
|
264
|
+
- **卡片文案精简** — 「规则列表」标题、优先级那句、自动取色旁边那句解释都删了(面板标题与 `兜底` 徽章已经说过);`添加图片` / `替换这张` / `移除图片` 收成 `添加` / `替换` / `移除`;`编辑位置` 移到**布局模式那一行的末尾**(它编辑的是图片如何填满这块区域,仍只在「适应」下出现)。
|
|
229
265
|
|
|
230
|
-
### v0.
|
|
266
|
+
### v0.7.1
|
|
231
267
|
|
|
232
|
-
-
|
|
233
|
-
-
|
|
268
|
+
- **主题色下放到图片** — 一条规则是一组图片的轮换,而这些图往往是为不同心情收的,不该共用同一个强调色。每张图现在带自己的 `color`,色轮、数值输入、灵感色板、「从本图提取」、吸管和「跟随系统主题」全都作用于**胶片条里选中的那张**。控件上方写明当前作用于哪张图,胶片条也标出哪些图已经各自有颜色,所以「我改了却没反应」不会是这个功能教给你的第一件事。
|
|
269
|
+
- **规则自己的颜色只剩一个含义** — **一张图都没有时它画什么**。它不是该规则图片的默认值:没有自己颜色的图**跟随系统主题**,而不是去继承规则色——正是这一点才让「绿的、红的、跟随系统」这样一组轮换成立。所以清空某张图的颜色只影响那一张,而规则色会留在「只剩它能画」的那个状态里等着。
|
|
270
|
+
- **升级不会丢主题色** — 这个版本之前写下的图片条目**根本没有** `color` 键,而这个区别正是加载器的全部依据:缺键 = 「从来没有自己的颜色」,于是规则色被抬升到它身上(只做一次,且在共享 sanitizer 里做,两半边行为一致);显式的 `null` = 「主动清空过」,原样保留。规则自身也留着那个颜色,所以把有主题色的规则清空后它照样能画。
|
|
271
|
+
- **轮换每一步都会重发调色板,这不是细节** — 换图原本只重画壁纸,于是「逐图主题色」会等到**别的**事情顺手重跑一次 apply 才出现:拖动调色盘,或切换模型。这正是这个插件已经踩过一次的现象(「更新的不太及时,只有我动调色盘或者切模型的时候才出现」),所以调色板的施加被收成一个函数,模型切换和轮换的每一步(定时器与手动「下一张」共用一个出口)都走它。
|
|
272
|
+
- **节日配色同样逐图** — 节日的固定配色同时写在节日条目和它的图片上、每次读取都由定义强制覆盖,于是「节日多图轮换、每张带自己的配色」只需要改 `HOLIDAYS` 和它的素材,而不需要在渲染层再开一条代码路径。
|
|
273
|
+
- **没有图片的规则只剩一个上传入口** — 上方那块大预览**本身就是按钮**了(点它上传,也可以把文件拖上去),而胶片条在一张图都没有时**完全不渲染**:它里面除了一块「添加图片」再没有别的东西,而胶片条是一组图片的列表,一张图都没有的状态下它没有存在的理由。其它都没动:带文字的 **添加图片** / **从网址** 按钮还在原位;有图片时预览依旧刻意保持惰性(拖到胶片条是新增,「替换这张」仍是显式的带文字按钮——正是这一点让预览不会吞掉用户无意的点击)。
|
|
274
|
+
- **配置格式升到 4,旧宿主依旧吃不掉配置** — `images[].color` 对 schema 3 的 sanitizer 是未知字段,而它会**按自己认识的键重建**每个图片条目:颜色会一直在内存里活到下一次重新加载,然后悄无声息地消失,日志里还什么都不会说。版本判断是简单的 `>=`,所以报出更低版本的宿主会让所有写入被扣住、面板里说明,直到重启 DSH。
|
|
275
|
+
- **门禁覆盖到它** — `check:node` 现在双向钉住抬升(缺键继承规则色、显式 `null` 不继承)、逐图颜色经过文件往返后各自独立、节日两处颜色都被强制,以及 `null` 是**写出键**而不是省略键(写成省略就是每次加载都悄悄把规则色继承回来)。`check:repaint` 还会在轮换步骤不再重发调色板时报错。
|
|
234
276
|
|
|
235
277
|
## Star History
|
|
236
278
|
|