dsh-background-by-model 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,28 @@
1
1
  # Changelog
2
2
 
3
- > The full release history lives here; the two most recent releases (v0.5.4 / v0.5.3) are also listed in [README.md](./README.md#recent-optimizations).
3
+ > The full release history lives here; the two most recent releases (v0.6.0 / v0.5.5) are also listed in [README.md](./README.md#recent-optimizations).
4
+
5
+ ## v0.6.0
6
+
7
+ - **Holiday backgrounds — one switch, not a settings block** — On 中秋节 and 国庆节 the wallpaper switches to that holiday's own image, with that holiday's own fixed theme color, and switches back the next morning. It ships ON, and the **only** UI it has is a single switch on the **Config** page. There is deliberately no card per holiday, no thumbnail and no image picker — the panels that briefly lived there made the feature read as something the user is expected to configure, which is exactly what it is not.
8
+ - **The switch is the off-ramp, and only an explicit `false` takes it down** — a config written before the feature existed, or one whose switch was never touched, keeps working.
9
+ - **A holiday with nothing to paint falls through instead of blanking the background** — the override needs all three of: the switch, a window that contains today, and actual bytes in that holiday's slot. The last one is the point — a switched-on holiday with no image leaves the model rules in charge.
10
+ - **中秋 is lunar, 国庆 is fixed, and both are read in Asia/Shanghai** — 中秋 is 农历八月十五 and moves across the Gregorian calendar (2024-09-17, 2025-10-06, 2026-09-25, 2027-09-15 …); National Day is the fixed October 1–7 window. The calendar comes from the browser's own ICU (no dependency at all). The zone is **pinned rather than taken from the machine**: the lunar day rolls over at Beijing midnight, so reading "today" off a UTC clock puts the festival on the wrong day for most of the planet — the day-boundary behaviour is pinned by a test at Beijing 23:59 / 00:01.
11
+ - **A leap eighth month cannot drag the festival a month late** — ICU marks a leap month by suffixing `bis`, so an exact `month === '8'` test is what keeps 中秋 on the **regular** eighth month. Checked against every leap-eighth-month year in 1900–2100 (1900, 1957, 1976, 1995, 2052, 2071, 2090): in 2052 the plugin fires on **2052-09-07**, never on the `8bis` fifteenth that falls in October that year.
12
+ - **中秋 wins the overlap with 国庆** — the two do collide (2025-10-06 was both). A single precise day is the better answer than one day inside a seven-day window.
13
+ - **The wallpapers ship inside the package, at one twelfth of the source art: 8.37 MB → 729 KB** — native resolution, WebP q80 (`中秋节` 1856 KB → 228 KB, `国庆节` 6716 KB → 500 KB). The tarball goes **401 KB → 1.12 MB**, and because the files are already-compressed images gzip cannot shrink them further — that is the entire cost of the feature, and the reason they are not shipped raw (raw would be a 21× heavier install, and every boot would then push a 6.7 MB base64 payload through the plugin's own RPC channel).
14
+ - **The wallpapers belong to the package, and nothing can replace them** — a holiday slot is read-only: the node half serves the bundled asset and never consults the data directory for it, and writing, deleting or URL-fetching into such a slot is refused. A file planted there is ignored by every read, which is what keeps the egg an egg instead of a setting.
15
+ - **An unused holiday costs nothing — and neither does an unused DAY** — with the override on by default and invisible, boot fetches only the holiday that can actually paint *today*; on any other day the 729 KB is never read at all. The rollover tick fetches the art if midnight lands *on* a holiday, since boot had no reason to. The node half looks the bundled file up lazily rather than loading it with the plugin.
16
+ - **The holiday theme colors are constants, not something read off the art** — 中秋 paints in `#384A77` and 国庆 in `#FFF6EB`: quoted as hex in the holiday definition, beside its slot and its wallpaper, converted to the rule shape's `[h, s, l]` by the shared schema, and forced on every read — so a hand-edited `color` in `theme-config.json` is replaced by the definition's on load. The extraction pass that decoded a full-size festival photo to re-derive a hue that was always going to be the same one is gone, and with it the "has this image been tried yet" bookkeeping; the palette is now a design decision that costs nothing at boot.
17
+ - **Theme exports carry rules only** — the holiday wallpapers are not in `dsh-background-by-model-theme.json`. They ship with the package, so exporting them would add ~970 KB of base64 to every file to move bytes that every install already has, into slots that refuse them anyway.
18
+ - **Two dependency-free checks now guard the parts that fail silently** — `pnpm check:holiday` (`scripts/holiday-check.ts`) pins the two windows, the Beijing day boundary, the leap-eighth-month behaviour, and the four gates `pickHoliday` applies before a holiday may take over; `pnpm check:ui` (`scripts/ui-strings-check.ts`) fails on a `t('…')` key missing from either dictionary, on a `dab-…` class with no rule in the stylesheet, and on a dictionary entry nothing references any more. A third, `pnpm check:node` (`scripts/node-half-check.mjs`), drives the BUILT node half through its real RPC handler with `DSH_HOME` pointed at a throwaway directory — so it covers the persistence layer without ever touching a real `~/.dsh`: the holiday block through the shared sanitizer, the packaged wallpapers served out of an empty slot, a holiday slot refusing writes, deletes and URL fetches while a file dropped into it is ignored, an ordinary rule slot still writing and deleting, and a hand-edited config unable to point a holiday at another rule's image. All three run on plain `node` (≥ 22.6 strips the types itself), so they need no dependency, no transformer and no browser, and `pnpm test` now runs them. The decision itself — `pickHoliday` — was extracted out of the plugin's apply closure into `src/holiday.ts` precisely so it could be tested at all.
19
+ - **`check:ui` immediately retired ten strings that had been dead since earlier releases** — `close`, `ruleMatchLabel`, `ruleFraming`, `ruleFramingLocked`, `colorHint`, `swatchTitle`, `hexCaption`, `extractColor`, `extractNoWp`, and the `holidayPreview` this release had just added.
20
+
21
+ ## v0.5.5
22
+
23
+ - **The color preview field prints its own hex, so it stops reading as a fourth text input** — The live preview swatch sits at the bottom of the same column as the H/S/L (R/G/B) number fields, at the same width and behind the same 1px border, and it was filled with the current color but carried no text at all. On a light color — the reported case was `#BADEE8`, L 82% — a blank bordered rectangle under three editable fields reads as one more field, and there is nothing to type into it. It now prints `#RRGGBB` in monospace, centered on the color itself, with its ink picked by **Rec.709 luma**, the same brightness measure the wallpaper extractor already uses, so a light fill gets dark ink and a dark one light ink and the value never disappears into its own background. The `scale(1.02)` hover went with the old reading: it advertised an interaction the field never had, which is most of why it looked clickable.
24
+ - **The hex caption above the HSL/RGB toggle is gone** — the preview field now prints that same value on the color itself, and printing it twice only added a second thing to explain.
25
+ - **The clear-color button is named for its action, not for the state it lands in** — It read **System theme** while its action was to clear the rule's custom color, so it read as a label for whatever control sat beside it rather than as a control. It is now **Follow system theme** (`跟随系统主题`), carrying the host's own appearance glyph (the sun, migrated from `IconLightOutline16`). This is the same label-versus-action mismatch fixed on the other side of this state in 0.5.3, where the no-color button became **Custom color**; the pair now names the two destinations instead of the two states.
4
26
 
5
27
  ## v0.5.4
6
28
 
package/CHANGELOG.zh.md CHANGED
@@ -1,6 +1,28 @@
1
1
  # 更新日志
2
2
 
3
- > 这里保留完整的历史记录;最近两个版本(v0.5.4 / v0.5.3)同时列在 [README.zh.md](./README.zh.md#近期优化) 里。
3
+ > 这里保留完整的历史记录;最近两个版本(v0.6.0 / v0.5.5)同时列在 [README.zh.md](./README.zh.md#近期优化) 里。
4
+
5
+ ## v0.6.0
6
+
7
+ - **节日特殊背景 —— 只有一个开关,不是一整块设置** —— 中秋与国庆当天,壁纸自动换成那个节日自己的图片、并配上该节日固定的主题色,第二天早上自动换回。功能默认就是开的,而它在界面上**只有一行**:**「配置」页**上一个开关。刻意不做每个节日的卡片、缩略图和选图入口 —— 那些面板短暂存在过,但它们让这个功能看起来像是等着用户去配置的东西,而它恰恰不是。
8
+ - **这个开关是「下车口」,只有显式 `false` 才算关** —— 功能存在之前写下的配置、或者从没动过开关的配置,都照常工作。
9
+ - **没图可画的节日会回落到规则,而不是把背景刷空** —— 覆盖需要同时满足三件事:开关、今天在窗口内、槽位里确实有字节。最后一条是关键:开了开关却没有图的节日,仍然让模型规则说了算。
10
+ - **中秋按农历、国庆按公历,两者都按 Asia/Shanghai 判定** —— 中秋是农历八月十五,在公历上逐年移动(2024-09-17、2025-10-06、2026-09-25、2027-09-15……);国庆是固定的 10 月 1–7 日。农历换算直接用浏览器自带的 ICU,**零依赖**。时区是**钉死的,不取机器本地时区**:农历日期在北京时间午夜翻页,用 UTC 时钟读「今天」会让地球上大多数人过错日子 —— 北京 23:59 / 00:01 的跨日行为有测试钉住。
11
+ - **闰八月不会把中秋拖后一个月** —— ICU 用后缀 `bis` 标记闰月,所以判等必须写死 `month === '8'`,这样中秋才永远落在**正**八月。已对 1900–2100 年间所有闰八月年份做过校验(1900、1957、1976、1995、2052、2071、2090):2052 年插件在 **2052-09-07** 生效,而不是那年落在十月的 `8bis` 十五。
12
+ - **中秋与国庆重叠时中秋优先** —— 两者确实会撞(2025-10-06 就是同一天)。一个精确的单日,比七天窗口里的某一天更值得采信。
13
+ - **壁纸内置在包里,压到原图的十二分之一:8.37 MB → 729 KB** —— 原生分辨率、WebP q80(中秋节 1856 KB → 228 KB,国庆节 6716 KB → 500 KB)。npm 包从 **401 KB 涨到 1.12 MB**;图片本身已是压缩格式,gzip 再压不动,这就是这个功能的全部代价 —— 也正是不能直接塞原图的原因(原图会让安装包重 21 倍,而且每次启动都要把 6.7 MB 的 base64 从插件自己的 RPC 通道推一遍)。
14
+ - **壁纸属于包,任何入口都换不掉** —— 节日槽位是只读的:node 半边直接送出内置资源,从不为此查询数据目录,写入、删除和抓取这些槽位一律被拒。丢进去的文件不会被任何一次读取采用 —— 这正是让这个彩蛋保持为彩蛋、而不是一项设置的原因。
15
+ - **没启用的节日不花任何代价 —— 没在过节的那天也不花** —— 既然覆盖默认开着又不显示,启动时就只取**今天真能生效**的那一个节日,别的日子那 729 KB 一个字节都不读;如果午夜正好跨进某个节日,由跨日轮询去补取(启动时没理由取它)。node 半边同样是按需才去找内置文件,而不是跟着插件一起加载。
16
+ - **节日主题色是常量,不是从图里读出来的** —— 中秋用 `#384A77`,国庆用 `#FFF6EB`:以十六进制写在节日定义里(就在槽位和壁纸旁边),由共享 schema 转成规则形状的 `[h, s, l]`,并在每次读取时强制覆盖 —— 所以手改 `theme-config.json` 里的 `color`,加载时也会被定义值换掉。原来那个「解码一张全尺寸节日照片,只为重新算出一个永远相同的色相」的取色流程,以及配套的「这张图试过了吗」记账,全部删除;配色现在是一项不花启动代价的设计决定。
17
+ - **导出的主题文件只带规则** —— 节日壁纸不在 `dsh-background-by-model-theme.json` 里。它们随包发布,导出意味着给每个文件多加约 970 KB base64,只为了搬运每份安装本来就有、而槽位还拒绝接收的字节。
18
+ - **三道零依赖校验,守住那些「静默出错」的地方** —— `pnpm check:holiday`(`scripts/holiday-check.ts`)钉住两个节日窗口、北京时间的跨日边界、闰八月的行为,以及 `pickHoliday` 允许节日接管背景之前必须通过的四道闸门;`pnpm check:ui`(`scripts/ui-strings-check.ts`)会在某个 `t('…')` 键只存在于一本字典、某个 `dab-…` 类在样式表里没有规则、或某条字典条目已经没人引用时直接失败。第三道 `pnpm check:node`(`scripts/node-half-check.mjs`)把**构建产物**里的 node 半边通过它真实的 RPC 处理器跑一遍,`DSH_HOME` 指向临时目录 —— 因此它覆盖了持久化层却从不碰真实的 `~/.dsh`:节日配置块穿过共享 sanitizer、内置壁纸从空槽位送出、节日槽位拒绝写入 / 删除 / 抓取而丢进去的文件被忽略、普通规则槽位照常写入和删除,以及手改过的配置无法把节日指向别的规则的图片。三道都用裸 `node` 跑(≥ 22.6 自己剥类型),不需要依赖、不需要转译器、也不需要浏览器,`pnpm test` 现在会一并执行。判定本身 —— `pickHoliday` —— 正是为了能测才从插件的 apply 闭包里抽到 `src/holiday.ts` 的。
19
+ - **`check:ui` 当场清掉了从早期版本遗留至今的十个死键** —— `close`、`ruleMatchLabel`、`ruleFraming`、`ruleFramingLocked`、`colorHint`、`swatchTitle`、`hexCaption`、`extractColor`、`extractNoWp`,以及本次刚加进去的 `holidayPreview`。
20
+
21
+ ## v0.5.5
22
+
23
+ - **颜色预览块现在把色值打在自己身上,不再像第四个输入框** — 这块实时预览色块位于 H/S/L(R/G/B)数字字段同一列的底部,宽度相同、边框同样 1px,填的是当前颜色却一个字都没有。颜色一浅就露馅 —— 报告里的例子是 `#BADEE8`,L 82%:三个可编辑字段下面一个空白边框矩形,读起来就是第四个空字段,而它并没有任何可输入的内容。现在把 `#RRGGBB` 用等宽字体居中打在颜色本身上,字色按 **Rec.709 亮度**选取(壁纸取色用的就是同一个亮度公式),浅色配深字、深色配浅字,值不会消失在自己的底色里。`scale(1.02)` 的 hover 也一并去掉:它给这个字段并不存在的交互做了广告,那正是它看起来能点的主要原因。
24
+ - **HSL/RGB 切换器上方那行色值文本删掉了** — 预览块已经把同一个值打在颜色上,再显示一遍只会多出一个需要解释的东西。
25
+ - **清空颜色的按钮改成按动作命名,而不是按它落到什么状态** — 它写的是**「系统主题」**,做的却是清掉这条规则的自定义颜色,于是读起来像是旁边那个控件的标签,而不像一个控件本身。现在叫**「跟随系统主题」**,并带上宿主自己的外观字形(太阳,从 `IconLightOutline16` 迁移而来)。这和 0.5.3 修掉的是同一个「文案写状态、动作做另一件事」的毛病,只是在这个状态的两侧各修一次:当时无颜色状态的按钮改成了**「自定义颜色」**;现在这一对命名的是两个去向,而不是两个状态。
4
26
 
5
27
  ## v0.5.4
6
28
 
package/README.md CHANGED
@@ -83,6 +83,19 @@ One interface, one config — only the current model differs:
83
83
  - **Bilingual** — Full Chinese / English UI with automatic locale detection.
84
84
  - **Theme Watchdog** — Re-asserts the custom theme if the host resets it.
85
85
 
86
+ ### Holiday backgrounds
87
+
88
+ 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.
89
+
90
+ - **How it behaves** — On 中秋节 and 国庆节 the wallpaper switches to that holiday's own image, with that holiday's own fixed theme color (中秋 `#384A77`, 国庆 `#FFF6EB`). The next day, the model rules take over again.
91
+ - **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.
92
+ - **National Day** — October 1–7.
93
+ - **Mid-Autumn wins the overlap** — the two do collide (2025-10-06 was both); the single precise day is the better answer.
94
+ - **A holiday with no image falls through** to the model rules rather than blanking the wallpaper.
95
+ - **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, so a hand-edited `color` in `theme-config.json` cannot repaint a festival either. Theme exports therefore carry your rules only.
97
+ - **Turning it off** — the switch on the Config page, or `"holidays": { "enabled": false }` in the same file.
98
+
86
99
  ### Per-rule settings
87
100
 
88
101
  - **Wallpaper** — Upload any image as this rule's wallpaper; each rule keeps its own file.
@@ -107,7 +120,7 @@ Settings → **Theme** now has three tabs:
107
120
  | --- | --- | --- |
108
121
  | **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 |
109
122
  | **Model Background** | Per rule | The ordered rule list, the live match readout, and each rule's wallpaper, theme color, layout mode, framing, opacity and blur |
110
- | **Config** | — | Import / export the whole rule set (`dsh-background-by-model-theme.json`) |
123
+ | **Config** | — | Import / export the whole rule set (`dsh-background-by-model-theme.json`), plus the single [holiday background](#holiday-backgrounds) switch |
111
124
 
112
125
  The old **Color** tab is gone — the theme color is now a property of each rule. The old **Background** tab became **Model Background**.
113
126
 
@@ -117,8 +130,9 @@ Data directory: `~/.dsh/.dsh-background-by-model-data/` (Windows: `C:\Users\<you
117
130
 
118
131
  | File | Contents |
119
132
  | --- | --- |
120
- | `theme-config.json` | The rule list plus the global Interface settings |
133
+ | `theme-config.json` | The rule list plus the global Interface settings and the holiday overrides |
121
134
  | `modelbg-<slot>` | The image of each rule, stored as raw bytes without a file extension |
135
+ | `modelbg-h-midautumn` / `modelbg-h-nationalday` | Not used: both holiday wallpapers ship inside the package and are served straight out of it |
122
136
 
123
137
  ## Installation
124
138
 
@@ -170,7 +184,11 @@ To mount a working copy into a profile instead, add it to the profile's `package
170
184
 
171
185
  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.
172
186
 
173
- `pnpm test` builds and typechecks (`tsdown && tsc -p tsconfig.json`).
187
+ `pnpm test` builds, typechecks and runs three checks (`tsdown && tsc -p tsconfig.json && node scripts/holiday-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:ui` / `pnpm check:node`:
188
+
189
+ - **`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.
190
+ - **`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, and a hand-edited config unable to redirect a holiday at another rule's image.
174
192
 
175
193
  ## FAQ
176
194
 
@@ -200,22 +218,19 @@ The other half of the same hole: while it owns a color the plugin **forces** `bo
200
218
 
201
219
  ## Recent Optimizations
202
220
 
203
- > Only the two most recent releases are listed here; older ones (v0.5.0 and earlier) live in [CHANGELOG.md](./CHANGELOG.md).
221
+ > Only the two most recent releases are listed here; older ones (v0.5.4 and earlier) live in [CHANGELOG.md](./CHANGELOG.md).
204
222
 
205
- ### v0.5.4
223
+ ### v0.6.0
206
224
 
207
- - **A brand-filled control takes its label from the host's on-brand ink, not from `brand-text`** — Following the system theme painted the active layout chip, the **+ Add rule** button and the settings brand tile from the host's own `--dsw-alias-brand-primary`, which is a **contrast ink rather than a hue**: near-black (`#0f1115`) in the host's light scheme, near-white (`#f9fafb`) in its dark one. Their labels read `--dsw-alias-brand-text`, which the host defines as that very same color in both schemes and never uses in its own CSS. On a dark system that made them filled boxes with invisible labels — white primary button, white active chip, white icon tile (measured at exactly `#f9fafb`) — and they were only ever readable while the plugin owned the palette and generated its own brand pair. All four now read `--dsw-alias-label-primary-inverted`, the host's own ink for a brand/contrast fill: `#fff` on the light scheme's black fill, `#353638` on the dark scheme's white one.
208
- - **The toggle's on-state knob was the same bug one size smaller** — a hard-coded `#fff` on a `--dsw-alias-brand-primary` track, i.e. a white knob on the white track the dark scheme paints there.
209
- - **The plugin's own dark palette had the mirror of it** — that branch deliberately keeps its brand fill at 50%+ lightness, while `--dsw-alias-label-primary-foreground` — the token the **host's** own primary buttons label themselves with over `--dsw-alias-button-primary-fill` — was hard-coded white. It now flips with the fill (one shared verdict with `brand-text`), and the light branch re-emits `label-primary-inverted` so the plugin's palette owns the pair instead of inheriting it from whichever scheme the host happens to be in.
225
+ - **Holiday backgrounds** — Settings → Config gains exactly one thing: a switch. While it is on and today is inside a holiday's window, that holiday's wallpaper replaces the rule list's answer and paints with that holiday's own fixed theme color. On by default, and there is deliberately no card per holiday, no thumbnail and no image picker — the art ships in the package and its slots are read-only, so it cannot be replaced from anywhere.
226
+ - **中秋 is lunar, 国庆 is fixed, both read in Asia/Shanghai** — 中秋 (八月十五) moves every year, so it comes from the browser's own ICU Chinese calendar, no dependency. The zone is pinned rather than taken from the machine: the lunar day turns over at Beijing midnight. A leap eighth month is excluded by an exact `month === '8'` test — verified against every leap-eighth-month year from 1900 to 2100 (2052 fires on 2052-09-07, not on that year's `8bis` fifteenth in October). Where the two overlap (2025-10-06), 中秋 wins.
227
+ - **The built-in wallpapers are compressed 12×: 8.37 MB → 729 KB** — native resolution WebP q80; the tarball goes 401 KB → 1.12 MB, and gzip cannot shrink already-compressed images further. That is the whole cost of the feature.
228
+ - **An unused holiday costs nothing — and neither does an unused day** — boot fetches only the holiday that can paint *today*, and the node half looks the asset up lazily rather than loading it with the plugin.
210
229
 
211
- ### v0.5.3
230
+ ### v0.5.5
212
231
 
213
- - **One palette at a time: clearing the theme color no longer leaves the previous skin behind** — The three redirects inside the settings dialog (`--dsw-alias-bg-layer-*` → plugin-owned variables) have **no host fallback**, and neither do the trajectory view's scope or the AppFrame columns' inline backgrounds; all of them were only ever cleaned up at teardown. Switching a rule back to **System theme** therefore left a dark interface with a light dialog and white-on-white text. Every surface is now driven by one palette source: the rule's own tokens while it has a color, and the host's own tokens read back out of the cascade and re-emitted with your alpha when it has none, handed back on the way out. The no-fallback redirects are gated on `html[data-dab-themed]`, so they are only live while the variables they read are actually written.
214
- - **Following the system theme keeps the wallpaper and the Interface sliders** — Clearing the color used to drop the whole token set, and the host's opaque `--dsw-alias-bg-base` then buried the wallpaper while every opacity/blur slider (main background, sidebar, cards, input, settings panel, trajectory view, right panel) silently stopped working. The surfaces are now the host's own colors times your opacity, so the wallpaper keeps showing through.
215
- - **Following the system theme hands the host's dark flag back** — While it owns a color the plugin forces `body[data-ds-dark-theme]` (removing it outright for a light palette), and the host only rewrites that flag when it projects a **theme snapshot** — it does not watch the attribute. A flag dropped by a light skin therefore sticks: the readback captures the host's light palette the moment the color is cleared, and the interface only turned dark once an unrelated slider drag re-ran an apply. The switch now hands the flag back synchronously in the host's own boolean form, using the scheme the host resolved (`ctx.theme`'s `active.colorScheme`, falling back to `html[data-ds-theme-source]` plus `prefers-color-scheme`); the one-second watchdog re-reads the host palette while a rule has no color, and the handback side no longer sits behind the 60 ms skin debounce (which made it read the plugin's own skin on its way out).
216
- - **The no-color button no longer does the opposite of its label** — It read **System theme** while its action was to install a hard-coded dark blue `#1D3463` (the same label means "clear → follow the system theme" one state over). It is now **Custom color**, and it runs the *Extract from this image* pass first, falling back to the seed swatch only when the image has nothing vivid.
217
- - **The token fingerprint is only recorded after the stylesheet write succeeds**, so a failed write is retried instead of pinning the interface to the old palette until another key happens to change.
218
- - **Fixed the mojibake dash in the `package.json` description**, which is what the npm page shows.
232
+ - **The color preview field prints its own hex** — it sat at the bottom of the H/S/L (R/G/B) column behind the same 1px border and carried no text, so on a light color (reported: `#BADEE8`, L 82%) it read as one more — blank — text input with nothing to type into it. It now prints `#RRGGBB` in monospace centered on the color, inked by **Rec.709 luma** (the wallpaper extractor's own brightness measure) so the value never disappears into its fill, and the hover `scale(1.02)` it never backed up is gone. The duplicate hex caption above the HSL/RGB toggle went with it.
233
+ - **The clear-color button says what it does** — **System theme** (a state) is now **Follow system theme** (the action), with the host's own sun glyph. It is the mirror of the 0.5.3 rename that turned the no-color button into **Custom color**.
219
234
 
220
235
  ## Star History
221
236
 
package/README.zh.md CHANGED
@@ -83,6 +83,19 @@
83
83
  - **中英双语** — 完整的中英文界面,自动跟随语言设置。
84
84
  - **主题守护** — 宿主重置主题后自动重新激活自定义主题。
85
85
 
86
+ ### 节日特殊背景
87
+
88
+ 小小一个,不是一整块设置:**「配置」页上一个开关,别的什么都没有**。它默认就是开的;到了日子背景自己换,第二天早上自己换回。
89
+
90
+ - **行为** — 中秋与国庆当天,壁纸换成那个节日自己的图片,并配上该节日固定的主题色(中秋 `#384A77`、国庆 `#FFF6EB`);第二天交还给模型规则。
91
+ - **中秋节** — 农历八月十五当天。它在公历上逐年移动(2024-09-17、2025-10-06、2026-09-25、2027-09-15……),所以是运行时按农历换算的,时区钉在 **Asia/Shanghai** —— 农历日期在北京时间午夜翻页,不是你那边的午夜。
92
+ - **国庆节** — 公历 10 月 1–7 日。
93
+ - **两者重叠时中秋优先** — 它们确实会撞(2025-10-06 就是同一天),一个精确的单日比七天窗口里的某一天更值得采信。
94
+ - **没有图的节日会回落到模型规则**,而不是把背景刷空。
95
+ - **内置壁纸** — 两个节日都自带一张压缩过的壁纸(原生分辨率下 228 KB 与 500 KB,原图共 8.37 MB)。只会去取**今天真能生效**的那一张,所以不在节日的配置一点都不会传。
96
+ - **图片和配色都不可替换,这是刻意的** — 一个要求用户去配置的彩蛋就不是彩蛋了。节日槽位是只读的:往 `modelbg-h-midautumn` / `modelbg-h-nationalday` 里放文件不会被任何一次读取采用,插件也拒绝写入、抓取和删除这两个槽位。主题色是节日定义里的常量、每次读取都强制覆盖,所以手改 `theme-config.json` 里的 `color` 也换不掉节日配色。导出的主题文件只包含你自己的规则。
97
+ - **不想要它** — 关掉「配置」页那个开关,或者在同一个文件里写 `"holidays": { "enabled": false }`。
98
+
86
99
  ### 每条规则自带的外观
87
100
 
88
101
  - **背景图片** — 上传任意图片作为该规则的壁纸,每条规则一张独立图片文件。
@@ -107,7 +120,7 @@
107
120
  | --- | --- | --- |
108
121
  | **界面** | 全局,所有模型共用 | 主背景、左侧栏、右侧栏、卡片面板、输入框与控件、设置面板、对话文本框、轨迹页的透明度与模糊度 |
109
122
  | **模型背景** | 每条规则 | 有序规则列表、顶部实时状态,以及每条规则的壁纸、主题色、布局模式、取景、透明度与模糊度 |
110
- | **配置** | — | 导入 / 导出整套规则(`dsh-background-by-model-theme.json`) |
123
+ | **配置** | — | 导入 / 导出整套规则(`dsh-background-by-model-theme.json`),外加[节日特殊背景](#节日特殊背景)那一个开关 |
111
124
 
112
125
  原先的 **「色彩」** 选项卡已删除——主题色改为每条规则自己的属性;原 **「背景」** 选项卡已改造成 **「模型背景」**。
113
126
 
@@ -117,8 +130,9 @@
117
130
 
118
131
  | 文件 | 内容 |
119
132
  | --- | --- |
120
- | `theme-config.json` | 规则列表 + 全局「界面」设置 |
133
+ | `theme-config.json` | 规则列表 + 全局「界面」设置 + 节日覆盖配置 |
121
134
  | `modelbg-<slot>` | 每条规则的图片,保存为原始字节,无扩展名 |
135
+ | `modelbg-h-midautumn` / `modelbg-h-nationalday` | 不使用:两张节日壁纸都随包发布,直接从包内送出 |
122
136
 
123
137
  ## 安装
124
138
 
@@ -170,7 +184,11 @@ pnpm run typecheck
170
184
 
171
185
  改完 `src/` 必须重新执行 `pnpm run bundle` 才会生效——挂载的 profile 加载的是 `lib/`。
172
186
 
173
- `pnpm test` 会构建并做类型检查(`tsdown && tsc -p tsconfig.json`)。
187
+ `pnpm test` 会构建、类型检查并跑三道校验(`tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs`)。三道都不需要依赖、不需要转译器、也不需要浏览器,也可以单独跑:`pnpm check:holiday` / `pnpm check:ui` / `pnpm check:node`。
188
+
189
+ - **`check:holiday`** — 两个节日窗口对照已知日期、北京时间的跨日边界(23:59 与 00:01)、1900–2100 年间的闰八月,以及 `pickHoliday` 允许节日接管背景之前必须通过的四道闸门。
190
+ - **`check:ui`** — 每个 `t('…')` 键在中英两本字典里都存在、两本字典键集完全一致、每个 `dab-…` 类在样式表里都有规则,且没有字典条目变成没人引用的死键。
191
+ - **`check:node`** — 把构建好的 node 半边通过它真实的 RPC 处理器跑一遍,`DSH_HOME` 指向临时目录:节日配置块穿过共享的 sanitizer、内置壁纸从空槽位被送出(且不会因此写进数据目录)、节日槽位拒绝写入 / 删除 / 抓取而丢进去的文件被忽略、普通规则槽位照常写入和删除,以及手改过的配置无法把节日指向别的规则的图片。
174
192
 
175
193
  ## 常见问题
176
194
 
@@ -200,22 +218,19 @@ pnpm run typecheck
200
218
 
201
219
  ## 近期优化
202
220
 
203
- > 这里只列最近两个版本;更早的版本(v0.5.0 及以前)见 [CHANGELOG.zh.md](./CHANGELOG.zh.md)。
221
+ > 这里只列最近两个版本;更早的版本(v0.5.4 及以前)见 [CHANGELOG.zh.md](./CHANGELOG.zh.md)。
204
222
 
205
- ### v0.5.4
223
+ ### v0.6.0
206
224
 
207
- - **填充上的文字改读宿主的「色块墨色」,不再用 `brand-text`** — 跟随系统主题时,布局模式的选中胶囊、「+ 新增规则」按钮和设置页的品牌方块,底色取自宿主自己的 `--dsw-alias-brand-primary`,而它**不是品牌色、是对比墨色**:宿主浅色档近黑(`#0f1115`),深色档近白(`#f9fafb`)。它们的文字读的是 `--dsw-alias-brand-text`,而宿主在两档里都把它定义成与底色**完全相同**的值、自己的 CSS 从不使用。于是在深色系统上就成了有底色却没有文字的方块 —— 白底主按钮、白底选中胶囊、白底图标块(实测正好 `#f9fafb`),只有插件自己持有一套调色板时才看得见。四处现在统一读 `--dsw-alias-label-primary-inverted`,也就是宿主自己画在品牌/对比色块上的墨色:浅色档 `#fff`,深色档 `#353638`。
208
- - **开关打开态的圆点是同一个毛病的小号版本** — 硬编码 `#fff` 画在 `--dsw-alias-brand-primary` 轨道上,而深色档那条轨道本身就是白的。
209
- - **插件自建的深色调色板是同一问题的镜像** — 那一档的品牌填充故意保持 50% 以上亮度,而**宿主自己的主按钮**用来写标签的 `--dsw-alias-label-primary-foreground`(画在 `--dsw-alias-button-primary-fill` 上)被写死成白色。现在它随填充亮度翻转(与 `brand-text` 共用一条判定),浅色分支也补上了 `label-primary-inverted`,让插件的调色板自己成对,而不是继承宿主当前恰好所处的档位。
225
+ - **节日特殊背景** — 设置 →「配置」页只多了一样东西:一个开关。开关打开且今天落在某个节日的窗口内时,该节日的壁纸会顶掉规则列表的结果,主题色用该节日固定的配色。默认开启;刻意不做每个节日的卡片、缩略图和选图入口,图片也无处可换 —— 两张壁纸随包发布,槽位只读。
226
+ - **中秋按农历、国庆按公历,都按 Asia/Shanghai 判定** — 中秋(八月十五)逐年移动,直接用浏览器自带的 ICU 农历换算,零依赖。时区是钉死的而非取机器本地:农历日期在北京时间午夜翻页。闰八月由 `month === '8'` 的精确判等排除 —— 已对 1900–2100 年间所有闰八月年份做过校验(2052 年在 2052-09-07 生效,而不是那年落在十月的 `8bis` 十五)。两者重叠时(2025-10-06)中秋优先。
227
+ - **内置壁纸压到原图的十二分之一:8.37 MB → 729 KB** — 原生分辨率 WebP q80;npm 包从 401 KB 涨到 1.12 MB,而图片本身已是压缩格式、gzip 再压不动。这就是这个功能的全部代价。
228
+ - **没启用的节日不花任何代价 —— 没在过节的那天也不花** — 启动时只取今天真能生效的那一个;node 半边按需查找内置文件,而不是跟着插件一起加载。
210
229
 
211
- ### v0.5.3
230
+ ### v0.5.5
212
231
 
213
- - **一次只用一个调色板:清空主题色不再留下上一次的皮肤** — 设置弹窗里那三段重定向(`--dsw-alias-bg-layer-*` → 插件私有变量)**没有宿主回退**,轨迹页与三栏的内联背景同理,而它们只在 teardown 时才清理。于是把主题色切回「系统主题」后会留下「深色界面 + 浅色弹窗、白底白字」。现在这些表面由一个调色板来源统一驱动:有主题色时用规则自己的令牌,没有时**读取宿主自己的令牌**再套上你的透明度,切换时一并交还;无回退的重定向用 `html[data-dab-themed]` 门控,只有变量确实写了才生效。
214
- - **无主题色也保住壁纸与「界面」页的滑块** — 以前清空主题色会撤掉整套令牌,宿主的实心 `--dsw-alias-bg-base` 就把壁纸整块盖住,主背景 / 左侧栏 / 卡片 / 输入框 / 设置面板 / 轨迹页 / 右侧栏的透明度与模糊也全部失效。现在表面是宿主的颜色乘你的透明度,壁纸继续从背后透出来。
215
- - **跟随系统主题会把宿主的深色标记交还回去** — 插件持有主题色时会强改 `body[data-ds-dark-theme]`(浅色皮肤直接删掉),而宿主只在**主题快照变化时**才重写它、并不监听这个属性。被删掉的标记因此会一直生效:切回系统主题时读到的是宿主的浅色调色板,界面要等你碰一下滑块才变深。现在切换时按宿主自己解析出的方案同步交还标记(`ctx.theme` 的 `active.colorScheme`,取不到则回退 `html[data-ds-theme-source]` + `prefers-color-scheme`),并且每秒的守护定时器在无主题色状态下也会重读一次宿主调色板;「交还」这一侧不再走 60ms 防抖,避免读到插件自己那套即将下线的皮肤。
216
- - **无颜色状态下的按钮不再做相反的事** — 那个按钮的文案是「系统主题」,动作却是塞一个硬编码的深蓝 `#1D3463`(同一条文案在有颜色时表示「清空 → 跟随系统主题」)。现在它叫 **「自定义颜色」**,并且先跑一次「从本图提取」,取不到鲜明颜色才退回种子色。
217
- - **令牌指纹只在样式表写入成功后记账** — 写入抛错时不再把指纹提前记下,避免界面卡在旧调色板上直到另一个 key 变化。
218
- - **`package.json` 描述里的乱码破折号修正**(它直接显示在 npm 页面上)。
232
+ - **颜色预览块把色值打在自己身上** — 它位于 H/S/L(R/G/B)那一列的底部,边框同样是 1px,却一个字都没有,于是颜色一浅(报告里是 `#BADEE8`,L 82%)就读成第四个空白输入框,而它没有任何可输入的内容。现在把 `#RRGGBB` 用等宽字体居中打在颜色上,字色按 **Rec.709 亮度**(壁纸取色用的同一个公式)选取,值不会消失在自己的底色里;那个它从未兑现的 `scale(1.02)` hover 也去掉了。HSL/RGB 切换器上方重复的那行色值一并删除。
233
+ - **清空颜色的按钮改成说它做什么** — **「系统主题」**(一个状态)现在是**「跟随系统主题」**(那个动作),并带上宿主自己的太阳字形。它与 0.5.3 把无颜色按钮改成**「自定义颜色」**的那次是同一种修正的两侧。
219
234
 
220
235
  ## Star History
221
236
 
Binary file
Binary file