dsh-background-by-model 0.7.10 → 0.8.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 +81 -90
- package/CHANGELOG.zh.md +76 -86
- package/README.md +87 -124
- package/README.zh.md +94 -131
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,15 +9,15 @@ 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
|
|
12
|
+
A **DeepSeek Harness** appearance plugin built around an **ordered list of model rules**: each rule carries its own images, opacity 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.
|
|
13
13
|
|
|
14
|
-
> **v0.3.0
|
|
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
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
## How Model Rules Work
|
|
19
19
|
|
|
20
|
-
A rule is a **match string** plus **one complete set of background settings**.
|
|
20
|
+
A rule is a **match string** plus **one complete set of background settings**. On a model switch the plugin reads the current session's model name, scans the list top-down, and applies the **first** rule whose match string appears anywhere in it (case-insensitive) — with **rule 1** as the fallback when nothing matches.
|
|
21
21
|
|
|
22
22
|
Example list:
|
|
23
23
|
|
|
@@ -35,14 +35,12 @@ Example list:
|
|
|
35
35
|
| `GLM-Turbo` | Rule 3 → Image C | only `glm` matches |
|
|
36
36
|
| `Kimi-K3` | Fallback → Rule 1 → Image A | no rule matches |
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
- **Order matters** — the first match wins, not the best match; drag rules to reorder them.
|
|
39
|
+
- **An empty match string** makes a rule skip matching and act purely as a fallback, so a single rule with an empty match string is "one wallpaper everywhere".
|
|
40
|
+
- The match string is compared against **provider, model id and display name** concatenated, so any of the three can select a rule.
|
|
41
|
+
- The current model is read from **this session's durable model selection** (the `modelSelection` projection the composer's own picker renders), so switching models inside a session takes effect immediately; the page shows what was read, and marks it **host default** when only the host-wide default was available.
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
- **An empty match string** makes a rule skip matching entirely and act purely as a fallback. A single rule with an empty match string is therefore "one wallpaper everywhere".
|
|
42
|
-
- The match string is compared against the concatenation of **provider, model id and model display name**, so any of the three can be used to select a rule.
|
|
43
|
-
- The current model is read from **this session's durable model selection** (the `modelSelection` projection — the same value the composer's model picker renders), so switching the model inside a session takes effect immediately. The Model Background page shows which model was read, and labels it **host default** when only the host-wide default was available instead of this session's own selection.
|
|
44
|
-
|
|
45
|
-
> Note: the example table above has **no `kimi`** entry, so switching to Kimi falls back to rule 1 = Image A, which looks like "nothing happened". Add a rule with the match string `kimi` to give Kimi its own wallpaper.
|
|
43
|
+
> The example above has **no `kimi`** entry, so Kimi falls back to rule 1 = Image A and looks like "nothing happened" — add a rule with the match string `kimi`.
|
|
46
44
|
|
|
47
45
|
## Screenshots
|
|
48
46
|
|
|
@@ -51,19 +49,19 @@ One interface, one config — only the current model differs:
|
|
|
51
49
|
<p align="center">
|
|
52
50
|
<img src="https://cdn.jsdelivr.net/gh/HarmlessFunny/assets@v0.1.0/dsh-background-by-model/wallpaper-deepseek.webp" alt="Appearance under DeepSeek" width="880">
|
|
53
51
|
<br/>
|
|
54
|
-
<em>DeepSeek V4.1 Flash · matched the rule
|
|
52
|
+
<em>DeepSeek V4.1 Flash · matched the rule containing <code>deepseek</code>: a light wallpaper with a matching theme color</em>
|
|
55
53
|
</p>
|
|
56
54
|
|
|
57
55
|
<p align="center">
|
|
58
56
|
<img src="https://cdn.jsdelivr.net/gh/HarmlessFunny/assets@v0.1.0/dsh-background-by-model/wallpaper-kimi.webp" alt="Appearance under Kimi" width="880">
|
|
59
57
|
<br/>
|
|
60
|
-
<em>Kimi K2.7 Code · matched the rule containing <code>kimi</code>: a dark wallpaper,
|
|
58
|
+
<em>Kimi K2.7 Code · matched the rule containing <code>kimi</code>: a dark wallpaper, and the whole interface reskinned with it</em>
|
|
61
59
|
</p>
|
|
62
60
|
|
|
63
61
|
<p align="center">
|
|
64
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">
|
|
65
63
|
<br/>
|
|
66
|
-
<em>One rule on the Model Background tab · the wallpaper, its opacity and its blur belong to the rule; the layout mode, the framing and the theme color
|
|
64
|
+
<em>One rule on the Model Background tab · the wallpaper, its opacity and its blur belong to the rule; the layout mode, the framing and the theme color belong to the image being edited</em>
|
|
67
65
|
</p>
|
|
68
66
|
|
|
69
67
|
<p align="center">
|
|
@@ -74,99 +72,66 @@ One interface, one config — only the current model differs:
|
|
|
74
72
|
|
|
75
73
|
### Model rules
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- **
|
|
80
|
-
- **
|
|
81
|
-
- **
|
|
82
|
-
- **
|
|
83
|
-
- **
|
|
84
|
-
- **Bilingual
|
|
85
|
-
- **Theme Watchdog** — Re-asserts the custom theme if the host resets it.
|
|
75
|
+
An ordered list of rules, matched first-hit against the current model's name, with rule 1 as the fallback.
|
|
76
|
+
|
|
77
|
+
- **Rules** — add, remove, reorder and name them; each carries its own match string, opacity and blur.
|
|
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
|
+
- **Live match readout** — the Model Background tab shows which model was read and which rule it resolved to.
|
|
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.
|
|
81
|
+
- **On disk, not in `localStorage`** — everything lives under `~/.dsh/.dsh-background-by-model-data/`, and old single-wallpaper configs migrate in place on first read.
|
|
82
|
+
- **Bilingual**, with automatic locale detection, plus a theme watchdog that re-asserts the theme if the host resets it.
|
|
86
83
|
|
|
87
84
|
### Image rotation
|
|
88
85
|
|
|
89
86
|
Per rule, and **off by default** — cycling spends real bandwidth, memory and battery, so nothing turns it on for you.
|
|
90
87
|
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
- **Only the active rule rotates
|
|
96
|
-
- **Next image** — steps the wallpaper immediately, for when you want to check the set. It sits with the rotation switch (beside *which image is on screen*) and works whether or not the timer is running.
|
|
97
|
-
- **What follows a switch is the rule — except everything that belongs to the picture** — opacity and blur belong to the rule and stay put. The **layout mode**, the **framing** and the **theme color** belong to the image, so a rotation can letterbox a tall screenshot and fill the landscape photo next to it, and can move through a green picture, a red one and a system-themed one with the interface palette following the wallpaper. The cross-fade between two images is the same one a model switch already used.
|
|
98
|
-
- **Layout mode, framing and color are per image** — 适应/填充 decides how *this* picture meets the viewport, a crop and an accent belong to it too, so each image keeps its own while sharing the rule's 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.
|
|
88
|
+
- **Dwell time** — 10 s / 30 s / 1 min / 5 min / 30 min, or any custom value in seconds (5 s – 24 h).
|
|
89
|
+
- **Order** — **In order** walks the list and wraps around; **Shuffle** picks among every image except the one on screen, so a tick never looks like it was missed.
|
|
90
|
+
- **Also step on a model switch** — a second trigger that runs no timer at all.
|
|
91
|
+
- **Next image** — steps the wallpaper on demand, whether or not the timer is running.
|
|
92
|
+
- **Only the active rule rotates**, on one timer for the whole plugin; a hidden tab stops the rotation with the pixels.
|
|
100
93
|
|
|
101
94
|
### Switch effect (global)
|
|
102
95
|
|
|
103
|
-
What a wallpaper **change** looks like
|
|
96
|
+
What a wallpaper **change** looks like — one setting on the Config page, shared by every rule, covering a model switch, a rotation step, **Next image** and a holiday taking over.
|
|
104
97
|
|
|
105
|
-
- **Four effects** — **Cross-fade** (the
|
|
106
|
-
- **Easing** — standard / linear / ease-out / ease-in-out
|
|
107
|
-
- **
|
|
108
|
-
-
|
|
109
|
-
- **
|
|
110
|
-
- **A preview swatch, because a wallpaper fading into itself is invisible** — replaying the effect on the wallpaper already on screen would show nothing, which is exactly the "I changed it and nothing happened" this plugin keeps designing out. The swatch replays the chosen effect with the duration a real switch would use right now, and picking a different effect replays it at once.
|
|
111
|
-
- **The interface colour changes on the same clock** — a theme colour is one rewrite of the token block, which used to change every surface in a single frame while the wallpaper faded. The alias tokens are registered as colours so they interpolate over this setting's duration and easing, including the surfaces that re-declare those tokens instead of inheriting them and the two the plugin paints inline. One deliberate exception: a **light↔dark flip switches instantly**, because that is a different palette rather than a tint of the same one, and interpolating between them walks the interface through mid-tones whose text is unreadable in both directions. The wallpaper keeps its own animation either way.
|
|
112
|
-
- **And it is not a slider** — the fade is armed only when the palette itself moved. A drag rewrites the same token block on every frame, and a live transition there would make the surface lag behind the pointer.
|
|
98
|
+
- **Four effects** — **Cross-fade** (the default), **Instant**, **Zoom in** and **Slide in**; zoom and slide only animate `transform` on the incoming layer, so neither costs a byte of extra transfer.
|
|
99
|
+
- **Easing and one duration** — standard / linear / ease-out / ease-in-out, and `0`–`3000 ms` where `0` is a hard cut rather than "unset". It replaced the per-rule `rotate.fadeMs`, which had no control anywhere; a hand-tuned value is folded into it on first read.
|
|
100
|
+
- **Reduced motion is a veto**, not a shorter animation.
|
|
101
|
+
- **The interface color fades on the same clock** — except a light↔dark flip, which switches instantly, because interpolating between two palettes walks through mid-tones whose text is unreadable in both directions.
|
|
102
|
+
- **A preview swatch** replays the effect, because a wallpaper fading into itself is invisible.
|
|
113
103
|
|
|
114
104
|
### Holiday backgrounds
|
|
115
105
|
|
|
116
|
-
|
|
106
|
+
One switch on the Config page and nothing else: on 中秋节 and 国庆节 the wallpaper switches to that holiday's own art and theme color, and back the next morning. It ships **off**, and the [recommended profile](#recommended-profile) turns it on.
|
|
117
107
|
|
|
118
|
-
-
|
|
119
|
-
- **Mid-Autumn Festival** — 农历八月十五, the day itself. It moves every year (2024-09-17, 2025-10-06, 2026-09-25, 2027-09-15 …), so it is resolved from the Chinese calendar at runtime, in **Asia/Shanghai** — the lunar day turns over at Beijing midnight, not yours.
|
|
120
|
-
- **National Day** — October 1–7.
|
|
121
|
-
- **Mid-Autumn wins the overlap** — the two do collide (2025-10-06 was both); the single precise day is the better answer.
|
|
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.
|
|
122
109
|
- **A holiday with no image falls through** to the model rules rather than blanking the wallpaper.
|
|
123
|
-
- **The art is downloaded, not shipped** —
|
|
124
|
-
- **Neither the art nor
|
|
125
|
-
- **Turning it on** — the switch on the Config page, or `"holidays": { "enabled": true }` in the same file. Only a real `true` counts: the feature is on because a configuration says so, not because nothing has been said. The per-holiday switches inside it (中秋 / 国庆) are a different thing and still default to on — they are details of a feature that is already switched on.
|
|
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.
|
|
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.
|
|
126
112
|
|
|
127
113
|
### Per-rule and per-image settings
|
|
128
114
|
|
|
129
|
-
- **Wallpaper** —
|
|
130
|
-
- **Theme
|
|
131
|
-
- **Layout
|
|
132
|
-
- **Framing** —
|
|
133
|
-
- **Background
|
|
134
|
-
- **Background Blur** — `0–60 px`, applied to the wallpaper layer.
|
|
115
|
+
- **Wallpaper** — one or more images per rule; the first is what the rule paints when nothing rotates.
|
|
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
|
+
- **Layout mode** — fit / fill / stretch / tile / center, stored per image; disabled on a rule that holds no picture, rather than hidden.
|
|
118
|
+
- **Framing** — drag to pan and scroll to zoom, editable in fit mode only, stored per image and stable across window resizes.
|
|
119
|
+
- **Background opacity** `0–100%` and **background blur** `0–60 px`, both per rule.
|
|
135
120
|
|
|
136
121
|
### Global settings (Interface tab)
|
|
137
122
|
|
|
138
|
-
- **
|
|
139
|
-
- **
|
|
140
|
-
- **
|
|
141
|
-
- **Conversation & Trajectory** — The message list is wrapped in a translucent card automatically, and the trajectory page gets whole-page opacity & blur controls, letting the wallpaper shine through the content.
|
|
142
|
-
- **Right Panel** — The column that slides in from the right when you open a file now has a card of its own: this card owns its opacity from the first drag, and until then it follows **Main background** — the state a config written while that was the shipped default is in, since a fresh install already owns the panel at the profile's `45%` — and its blur stacks on top of the main-background blur. While closed it takes no space and costs nothing.
|
|
123
|
+
- **Opacity and blur per interface part** — main background, left panel, right panel, cards & menus, input & controls, settings panel, conversation text box and trajectory page, each with its own slider.
|
|
124
|
+
- **A fresh install already looks like the author's own profile**, so a first run needs no download to look right.
|
|
125
|
+
- **Independent of the theme color** — a rule with no color keeps every one of these sliders live.
|
|
143
126
|
|
|
144
127
|
### Recommended profile
|
|
145
128
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
**Every Interface number in it is what the plugin already ships with** — each opacity, blur and tint alpha in `theme-config.json` is the number a fresh install starts on (see [Global settings](#global-settings-interface-tab)) — so on a new install this button adds the rules and the pictures. Two settings it brings that a fresh install does not have are outside that tab: the [festival switch](#holiday-backgrounds), which ships off and this profile turns on, and the [switch duration](#switch-effect-global), which the profile sets to its 3000 ms maximum where the shipped default is the 320 ms cross-fade every earlier release used. On a store whose sliders you have already moved, adopting it resets those too: it is a whole configuration, not a patch.
|
|
149
|
-
|
|
150
|
-
**It is hosted in the store's own shape**, not as one bundled document:
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
preset/
|
|
154
|
-
├── preset.json the shape gate — {"version": 6}
|
|
155
|
-
├── theme-config.json the rules and the interface settings (6.5 KB)
|
|
156
|
-
└── modelbg-m1 … modelbg-m7 each wallpaper as its raw bytes
|
|
157
|
-
```
|
|
129
|
+
One click on the Config tab downloads the author's own configuration — rules, wallpapers and interface settings — from the project's assets repository.
|
|
158
130
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
- **You are asked second, not first.** The download happens before the confirmation, so the question can say what is actually in hand — *7 rules · 7 wallpapers · 2.9 MB* — instead of asking about a file that may never arrive. If the download fails, nothing has happened yet.
|
|
164
|
-
- **One missing wallpaper fails the whole thing**, and says which. A profile that quietly arrived without a picture — because a file was renamed where it is hosted — would be exactly the silent failure that pinning the festival art to a release tag exists to prevent; naming the file keeps that failure loud, and that loudness is what makes a profile safe to read from a host this plugin does not control.
|
|
165
|
-
- **Every file retries on its own, and a failed attempt keeps what it fetched.** A file used to get exactly two chances (one per mirror), so with the first few wallpapers down and the next hitting a 502 or a CDN hiccup the whole profile failed and the button started from zero again. Each file now gets up to three rounds (400 ms and then 800 ms apart), while three failures are not retried at all — "the file is not there" (the same answer twice), "the bytes are over the cap" (the same bytes again), and "a full minute of silence" (both mirrors were already asked inside that same round, which is what keeps the worst case at three minutes rather than six). What did arrive is kept for the next press, so that press only fetches what is missing — as long as the config it re-fetches is **the same value** as the one those bytes came from (this profile is read at one version of the assets repository, but that version's tag can be re-pointed and an edge that has not been purged will still answer with the previous bytes, so two different revisions must not be glued together). The strictness is unchanged: the first file that cannot be had still fails the whole attempt, and still says which one.
|
|
166
|
-
- **The store is emptied before the profile is written.** `nextSlot` allocates slots as `m1`, `m2`, … — a counter, not a random token — so a downloaded profile and the store it lands in collide on those names as a matter of course. "Replace everything with this" is only true if the everything is genuinely gone, and this is also why there is no undo.
|
|
167
|
-
- **The wipe runs after the download, never before it.** An unreachable CDN must not cost you your configuration on the way to a profile that never arrived — which matters more now that the download is several transfers rather than one.
|
|
168
|
-
- **The festival art is not touched.** `holiday-cache/` is downloaded art that belongs to no slot; it is not part of anybody's configuration, and re-fetching it would be a network round trip for nothing. The config *does* name the two holiday slots (they are part of any config), and they are dropped from the request set rather than asked for: no `modelbg-h-*` file can exist.
|
|
169
|
-
- **It is referenced at the same release tag as the festival art** — and it used to be a branch, deliberately: a profile fails *loudly* (you are looking at the button when it happens) where a holiday fails silently, so pinning bought nothing there and a branch let a bad recommendation be corrected without a plugin release. What a branch also bought was a plugin whose bytes could change under you between the install and the click; with one tag covering the screenshots, the festival art and this profile, one release of the assets repository is now one version of everything the plugin fetches. The price is that correcting one JSON file costs a tag in `assets`, a value in `src/index.ts` and a release on npm — and that moving a tag does not reach jsDelivr's edge by itself (the reference is served as immutable, and the purge tool is the only way to re-point it), so a correction is a new tag rather than a force-push of the old one. The risk that is real — a profile written for a config shape this build cannot read — is what `preset.json` is for: it is refused before a single wallpaper is requested, and long before anything is deleted.
|
|
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`.
|
|
132
|
+
- **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
|
+
- **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 destructive action in the panel.
|
|
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.
|
|
170
135
|
|
|
171
136
|
## Settings
|
|
172
137
|
|
|
@@ -178,7 +143,7 @@ Settings → **Theme** now has three tabs:
|
|
|
178
143
|
| **Model Background** | Per rule, per image | The ordered rule list, the live match readout, and each rule's images (filmstrip), **per-image layout mode, framing and theme color**, plus the rule's opacity, blur and [rotation](#image-rotation) |
|
|
179
144
|
| **Config** | — | The global [switch effect](#switch-effect-global), the whole rule set as a file — [recommended profile](#recommended-profile), import and export (`dsh-background-by-model-theme.json`) — plus the single [holiday background](#holiday-backgrounds) switch |
|
|
180
145
|
|
|
181
|
-
The old **Color** tab is gone — the theme color is now a property of each rule
|
|
146
|
+
The old **Color** tab is gone — the theme color is now a property of each rule — and the old **Background** tab became **Model Background**.
|
|
182
147
|
|
|
183
148
|
## Storage
|
|
184
149
|
|
|
@@ -188,7 +153,7 @@ Data directory: `~/.dsh/.dsh-background-by-model-data/` (Windows: `C:\Users\<you
|
|
|
188
153
|
| --- | --- |
|
|
189
154
|
| `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 |
|
|
190
155
|
| `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 |
|
|
191
|
-
| `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`).
|
|
156
|
+
| `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 |
|
|
192
157
|
|
|
193
158
|
## Installation
|
|
194
159
|
|
|
@@ -196,10 +161,10 @@ Data directory: `~/.dsh/.dsh-background-by-model-data/` (Windows: `C:\Users\<you
|
|
|
196
161
|
|
|
197
162
|
```sh
|
|
198
163
|
dsh plugin --profile web add dsh-background-by-model
|
|
199
|
-
# or install straight from the repository:
|
|
200
|
-
dsh plugin --profile web add github:HarmlessFunny/dsh-background-by-model
|
|
201
164
|
```
|
|
202
165
|
|
|
166
|
+
This is the only source — a `github:` install would clone a tree with no `lib/` in it, and nothing there would build one.
|
|
167
|
+
|
|
203
168
|
Then restart `dsh web`.
|
|
204
169
|
|
|
205
170
|
The plugin appears as a **"Theme"** section in Settings.
|
|
@@ -207,22 +172,23 @@ The plugin appears as a **"Theme"** section in Settings.
|
|
|
207
172
|
### Method 2: npx (No Global Install)
|
|
208
173
|
|
|
209
174
|
```sh
|
|
210
|
-
npx @deepseek-ai/dsh plugin --profile web add
|
|
175
|
+
npx @deepseek-ai/dsh plugin --profile web add dsh-background-by-model
|
|
211
176
|
npx @deepseek-ai/dsh web
|
|
212
177
|
```
|
|
213
178
|
|
|
214
179
|
### Method 3: Local Build (Development)
|
|
215
180
|
|
|
216
|
-
|
|
181
|
+
`lib/` is **not** in the repository — it is built from `src/`, by the package's `prepack` hook when the tarball is packed, and by you for a working copy. Clone, install, then build:
|
|
217
182
|
|
|
218
183
|
```sh
|
|
219
184
|
git clone https://github.com/HarmlessFunny/dsh-background-by-model.git
|
|
220
185
|
cd dsh-background-by-model
|
|
221
186
|
pnpm install
|
|
222
|
-
pnpm
|
|
223
|
-
pnpm run typecheck
|
|
187
|
+
pnpm test # builds lib/, typechecks, runs the ten checks
|
|
224
188
|
```
|
|
225
189
|
|
|
190
|
+
`pnpm run bundle` rebuilds `lib/` on its own. Either has to run before the plugin can load: a fresh clone has no `lib/`.
|
|
191
|
+
|
|
226
192
|
To mount a working copy into a profile instead, add it to the profile's `package.json` and register the bundle:
|
|
227
193
|
|
|
228
194
|
```jsonc
|
|
@@ -240,47 +206,47 @@ To mount a working copy into a profile instead, add it to the profile's `package
|
|
|
240
206
|
|
|
241
207
|
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.
|
|
242
208
|
|
|
243
|
-
`pnpm test` builds, typechecks and runs ten checks
|
|
209
|
+
`pnpm test` builds, typechecks and runs ten checks, none of which needs a dependency, a transformer or a browser. Each can also be run on its own with `pnpm check:holiday` / `pnpm check:rotation` / `pnpm check:transition` / `pnpm check:repaint` / `pnpm check:code-plate` / `pnpm check:image-mode` / `pnpm check:rules` / `pnpm check:preset` / `pnpm check:ui` / `pnpm check:node`:
|
|
244
210
|
|
|
245
|
-
- **`check:holiday`** — the two holiday windows
|
|
246
|
-
- **`check:rotation`** —
|
|
247
|
-
- **`check:transition`** —
|
|
248
|
-
- **`check:repaint`** —
|
|
249
|
-
- **`check:code-plate`** — the code block's plate:
|
|
250
|
-
- **`check:image-mode`** — the
|
|
251
|
-
- **`check:rules`** — the two decisions behind the tab strip, which
|
|
252
|
-
- **`check:preset`** — the
|
|
253
|
-
- **`check:ui`** —
|
|
254
|
-
- **`check:node`** — the built node half
|
|
211
|
+
- **`check:holiday`** — the two holiday windows, the Beijing day boundary, leap eighth months across 1900–2100, and the four gates `pickHoliday` applies.
|
|
212
|
+
- **`check:rotation`** — `nextIndex` / `isRotating`: no walking off the end, shuffle never repeating the image on screen, and nothing called "rotating" with nothing to rotate.
|
|
213
|
+
- **`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.
|
|
214
|
+
- **`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.
|
|
215
|
+
- **`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.
|
|
216
|
+
- **`check:image-mode`** — the layout mode's lift onto the image, and which image a control edits against which the render layer reads.
|
|
217
|
+
- **`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.
|
|
218
|
+
- **`check:preset`** — the recommended profile's retry policy and what a failed attempt holds for the next one, with both halves shown using them.
|
|
219
|
+
- **`check:ui`** — both dictionaries carrying the same keys, every `dab-…` class styled, no dead entry, and every `var(--dsw-…)` published by the installed theme.
|
|
220
|
+
- **`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, and the recommended profile end to end.
|
|
255
221
|
|
|
256
222
|
## FAQ
|
|
257
223
|
|
|
258
224
|
**Every model looks the same — why?**
|
|
259
|
-
|
|
225
|
+
Rule 1 probably has an empty match string, which makes it a pure fallback holding the whole list. Give it a match string, or add more specific rules above it.
|
|
260
226
|
|
|
261
227
|
**`GLM-Flash` picked the wrong rule.**
|
|
262
|
-
Matching is first-hit, not best-hit
|
|
228
|
+
Matching is first-hit, not best-hit: put the more specific rule higher in the list, or reorder.
|
|
263
229
|
|
|
264
230
|
**Where are my wallpapers?**
|
|
265
231
|
In `~/.dsh/.dsh-background-by-model-data/`, one `modelbg-<slot>` file per image plus `theme-config.json`.
|
|
266
232
|
|
|
267
233
|
**I turned the rotation on and the background never changed.**
|
|
268
|
-
Three things can hold it: the rule needs two or more images, the tab has to be visible
|
|
234
|
+
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.
|
|
269
235
|
|
|
270
236
|
**Does rotating cost anything?**
|
|
271
|
-
Yes,
|
|
237
|
+
Yes, which is why it is off by default: each image is stored at full fidelity and every switch repaints the whole interface. Two to five images is the sweet spot — boot only reads each rule's first image, so the cost is memory and disk rather than start-up time.
|
|
272
238
|
|
|
273
239
|
**Why did the theme color stop working after I removed a rule's images?**
|
|
274
|
-
That was the old behaviour, and
|
|
240
|
+
That was the old behaviour, and it is fixed: a rule now counts while it has **an image, or a theme color of its own**, so it still matches and still serves as the fallback while painting the interface alone.
|
|
275
241
|
|
|
276
242
|
**I updated the plugin and the panel says my edits are not being saved.**
|
|
277
|
-
The browser half and the host half
|
|
243
|
+
The browser half and the host half update separately, so a refreshed page can run the new client against DSH's process still running the previous node half, which cannot read the new config shape. The client holds its writes until you restart DSH.
|
|
278
244
|
|
|
279
245
|
**I set a color for one image and the other images did not change.**
|
|
280
|
-
That is the design: since 0.7.1 a theme color belongs to the **image**,
|
|
246
|
+
That is the design: since 0.7.1 a theme color belongs to the **image**, so a rotation can move through pictures with different accents. A rule with **no image at all** is the one case where the controls edit the rule's own color.
|
|
281
247
|
|
|
282
248
|
**Where did the theme color I set before this update go?**
|
|
283
|
-
Nowhere: an image written before per-image colors has no color
|
|
249
|
+
Nowhere: an image written before per-image colors has no `color` key at all, which is how the loader tells "never had one" from "cleared on purpose", and it lifts the rule's color onto every such image.
|
|
284
250
|
|
|
285
251
|
**Can I share a setup?**
|
|
286
252
|
Yes — export it from the **Config** tab; the JSON inlines every rule's image.
|
|
@@ -288,34 +254,31 @@ Yes — export it from the **Config** tab; the JSON inlines every rule's image.
|
|
|
288
254
|
**Are video or generated backgrounds supported?**
|
|
289
255
|
No. Both were removed in 0.3.0; each rule uses a static image.
|
|
290
256
|
|
|
291
|
-
**After switching a rule back to "System theme", why is the settings dialog a different palette
|
|
292
|
-
That was a bug
|
|
257
|
+
**After switching a rule back to "System theme", why is the settings dialog a different palette?**
|
|
258
|
+
That was a bug and it is fixed: every surface is now driven by one palette source, so clearing the color reads the host's own tokens back and re-emits them with your alpha.
|
|
293
259
|
|
|
294
260
|
**Why does "Custom color" produce a color the moment I press it?**
|
|
295
|
-
Pressing it means leaving the system-theme state, so it has to hand out a starting color
|
|
261
|
+
Pressing it means leaving the system-theme state, so it has to hand out a starting color — it runs the extract-from-image pass first and falls back to a default swatch only when the image has nothing vivid.
|
|
296
262
|
|
|
297
263
|
**After switching back to "System theme" the interface stays light until I drag a slider — why?**
|
|
298
|
-
The other half of the same
|
|
264
|
+
The other half of the same bug, also fixed: the plugin forces `body[data-ds-dark-theme]` while it owns a color, and the host only rewrites that flag when it projects a theme snapshot. The switch now hands the flag back synchronously, and a one-second watchdog re-reads the host palette.
|
|
299
265
|
|
|
300
266
|
**I picked a new switch effect and the wallpaper still cuts instantly.**
|
|
301
|
-
|
|
267
|
+
The effect is **Instant**, or the duration is `0` — an effect and a duration are two ways of asking for the same hard cut, and the effect wins. Outside the plugin, a system **reduced motion** setting means nothing animates at all.
|
|
302
268
|
|
|
303
269
|
## Recent Optimizations
|
|
304
270
|
|
|
305
|
-
> Only the two most recent releases are listed here; older ones
|
|
271
|
+
> Only the two most recent releases are listed here; older ones live in [CHANGELOG.md](./CHANGELOG.md).
|
|
306
272
|
|
|
307
|
-
### v0.
|
|
273
|
+
### v0.8.0
|
|
308
274
|
|
|
309
|
-
- **The
|
|
310
|
-
- **
|
|
311
|
-
- **`check:node` pins both halves of it.** The suite's `REF` is now one value used by the two URL assertions *and* by the cache path, and three assertions were added: art missing from the pinned generation is re-downloaded rather than read stale, a planted `v0.0.9` directory is gone once the new art lands, and the generation in use is left exactly where it was.
|
|
275
|
+
- **The bundle is built when the tarball is packed, not committed** — `lib/` was ~1 MB of generated JavaScript per release and effectively all of this repository's growth; it is now gitignored and built by `prepack`, so what npm serves is always built from the tagged source.
|
|
276
|
+
- **Installing straight from a GitHub checkout is gone** — that tree has no `lib/` and nothing in it builds one, so npm is the only source. Releases also now come from GitHub Actions over OIDC and carry build provenance.
|
|
312
277
|
|
|
313
|
-
### v0.7.
|
|
278
|
+
### v0.7.10
|
|
314
279
|
|
|
315
|
-
- **
|
|
316
|
-
- **
|
|
317
|
-
- **The recommended profile deliberately gives up "fix it without a release"** — it used to follow a branch so a bad recommendation could be corrected in the assets repository instead of waiting for a plugin release, and the reasoning was that a profile fails *loudly* (you are looking at the button) where a holiday fails silently, so immutability bought nothing there. What it cost was a plugin whose bytes could change under you between the install and the click; with one tag covering the screenshots, the art and the profile, that is no longer true of any of them. Correcting one JSON file now costs a tag in `assets`, a value in `src/index.ts` and a release on npm. Note what a tag is **not**: it can be re-pointed, and jsDelivr serves a version reference as immutable, so re-pointing one means a purge request rather than a push — which is why `presetKey` survives this change, since an edge that has not been purged can still answer with the previous bytes of the same URL, and a press that resumes must not glue two revisions together.
|
|
318
|
-
- **`check:node` pins the tag on both features.** Its `revision()` helper used to look for a 40-character SHA, which cannot see a tag at all; it now lifts the reference out beside the repository name (`HarmlessFunny/assets@<ref>` in the jsDelivr shape, `HarmlessFunny/assets/<ref>/` in the raw one) so the two mirrors can still be compared, and the two assertions that used to read "a commit, never a branch" and "follows the branch" now both require `v0.1.0`.
|
|
280
|
+
- **The festival art's cache is keyed by the pinned release** — `holiday-cache/<tag>/<asset>`, so a build pinning a new tag starts from a directory nobody has written to instead of being answered with the previous generation's bytes.
|
|
281
|
+
- **A superseded generation is pruned**, at the end of a successful cache write and never before it, so a release that turns out to be unreachable cannot cost you the art you already have.
|
|
319
282
|
|
|
320
283
|
## Star History
|
|
321
284
|
|