dsh-dream-skin 0.4.13 → 0.4.15

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/README.md CHANGED
@@ -377,7 +377,7 @@ bundle(`@deepseek-ai/dsh-client-runtime/client`、…)。
377
377
  > 每天自动更新(GitHub Actions)。左轴:**累计下载量**(青色);右轴:**Star 数**(紫色)——两个量级不同,因此使用独立的双纵轴。
378
378
 
379
379
  <p align="center">
380
- <img src="docs/stats.png" alt="dsh-dream-skin 每日 Star × 累计下载量成长曲线" width="900"/>
380
+ <img src="docs/stats.png?v=2" alt="dsh-dream-skin 每日 Star × 累计下载量成长曲线" width="900"/>
381
381
  </p>
382
382
 
383
383
  *数据每 24 小时自动采集一次:下载量来自 [npm 官方 API](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin),Star 来自 [GitHub API](https://github.com/RevolutionLA/dsh-dream-skin/stargazers)。*
@@ -383,3 +383,15 @@ Ein Sicherheitsproblem gefunden? Öffne kein öffentliches Issue — siehe die [
383
383
 
384
384
  - Architektur- & API-Referenz: das offizielle Client-Paket [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme) von DeepSeek Harness.
385
385
  - Konzept-Hommage: [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin).
386
+
387
+ ---
388
+
389
+ ## 📈 Wachstumskurve
390
+
391
+ > Täglich automatisch aktualisiert (GitHub Actions). Linke Achse: **kumulierte Downloads** (türkis); rechte Achse: **Sternezahl** (violett) — sehr unterschiedliche Größenordnungen, daher zwei unabhängige Y-Achsen.
392
+
393
+ <p align="center">
394
+ <img src="../../docs/stats.png?v=2" alt="Tägliche Wachstumskurve Star × kumulierte Downloads von dsh-dream-skin" width="900"/>
395
+ </p>
396
+
397
+ *Die Daten werden alle 24 Stunden gesammelt: Downloads über die [offizielle npm-API](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin), Sterne über die [GitHub-API](https://github.com/RevolutionLA/dsh-dream-skin/stargazers).*
@@ -383,3 +383,16 @@ Found a security issue? Don't open a public issue — see the [Security Policy](
383
383
  - Architecture & API reference: the official DeepSeek Harness
384
384
  [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme) client package.
385
385
  - Concept homage: [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin).
386
+
387
+ ---
388
+
389
+ ## 📈 Growth chart
390
+
391
+ > Updated automatically every day (GitHub Actions). Left axis: **cumulative downloads** (teal); right axis:
392
+ > **Star count** (purple) — two very different magnitudes, so each has its own independent Y-axis.
393
+
394
+ <p align="center">
395
+ <img src="../../docs/stats.png?v=2" alt="dsh-dream-skin daily Stars × cumulative downloads growth chart" width="900"/>
396
+ </p>
397
+
398
+ *Data is collected every 24 hours: downloads from the [npm API](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin), Stars from the [GitHub API](https://github.com/RevolutionLA/dsh-dream-skin/stargazers).*
@@ -390,3 +390,15 @@ que el proyecto se descubra y se mantenga. ¿Quieres contribuir con themes / un
390
390
  - Referencia de arquitectura y API: el paquete de cliente
391
391
  [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme) oficial de DeepSeek Harness.
392
392
  - Homenaje conceptual: [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin).
393
+
394
+ ---
395
+
396
+ ## 📈 Curva de crecimiento
397
+
398
+ > Actualizada automáticamente cada día (GitHub Actions). Eje izquierdo: **descargas acumuladas** (verde); eje derecho: **número de Stars** (púrpura) — magnitudes muy distintas, por eso cada uno tiene su propio eje Y independiente.
399
+
400
+ <p align="center">
401
+ <img src="../../docs/stats.png?v=2" alt="Curva de crecimiento diario Star × descargas acumuladas de dsh-dream-skin" width="900"/>
402
+ </p>
403
+
404
+ *Los datos se recogen cada 24 horas: las descargas desde la [API oficial de npm](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin), y las Stars desde la [API de GitHub](https://github.com/RevolutionLA/dsh-dream-skin/stargazers).*
@@ -382,3 +382,15 @@ Vous avez trouvé un problème de sécurité ? N'ouvrez pas d'issue publique —
382
382
  - Référence d'architecture & d'API : le package client
383
383
  [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme) officiel de DeepSeek Harness.
384
384
  - Hommage conceptuel : [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin).
385
+
386
+ ---
387
+
388
+ ## 📈 Courbe de croissance
389
+
390
+ > Mise à jour automatique chaque jour (GitHub Actions). Axe gauche : **téléchargements cumulés** (turquoise) ; axe droit : **nombre d'étoiles** (violet) — des ordres de grandeur très différents, d'où deux axes Y indépendants.
391
+
392
+ <p align="center">
393
+ <img src="../../docs/stats.png?v=2" alt="Courbe de croissance quotidienne Star × téléchargements cumulés de dsh-dream-skin" width="900"/>
394
+ </p>
395
+
396
+ *Les données sont collectées toutes les 24 h : téléchargements via l'[API officielle npm](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin), étoiles via l'[API GitHub](https://github.com/RevolutionLA/dsh-dream-skin/stargazers).*
@@ -347,3 +347,15 @@ Issue と PR は大歓迎です![コントリビューションガイド](../.
347
347
 
348
348
  - アーキテクチャ & API リファレンス:公式 DeepSeek Harness の [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme) クライアントパッケージ。
349
349
  - コンセプトのオマージュ:[Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin)。
350
+
351
+ ---
352
+
353
+ ## 📈 成長カーブ
354
+
355
+ > 毎日自動更新(GitHub Actions)。左軸:**累計ダウンロード数**(青);右軸:**Star 数**(紫)——桁が大きく異なるため、それぞれ独立した双軸を使用しています。
356
+
357
+ <p align="center">
358
+ <img src="../../docs/stats.png?v=2" alt="dsh-dream-skin 毎日の Star × 累計ダウンロード数 成長カーブ" width="900"/>
359
+ </p>
360
+
361
+ *データは 24 時間ごとに自動取得:ダウンロード数は [npm 公式API](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin)、Star 数は [GitHub API](https://github.com/RevolutionLA/dsh-dream-skin/stargazers) から取得しています。*
@@ -383,3 +383,15 @@ Issues와 PR을 환영합니다! [기여 가이드](../../CONTRIBUTING.md)를
383
383
  - 아키텍처 & API 참고: 공식 DeepSeek Harness
384
384
  [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme) 클라이언트 패키지.
385
385
  - 컨셉 오마주: [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin).
386
+
387
+ ---
388
+
389
+ ## 📈 성장 곡선
390
+
391
+ > 매일 자동 업데이트(GitHub Actions). 왼쪽 축: **누적 다운로드 수**(청록); 오른쪽 축: **Star 수**(보라) — 규모가 크게 달라 각각 독립된 이중 축을 사용합니다.
392
+
393
+ <p align="center">
394
+ <img src="../../docs/stats.png?v=2" alt="dsh-dream-skin 매일 Star × 누적 다운로드 수 성장 곡선" width="900"/>
395
+ </p>
396
+
397
+ *데이터는 24시간마다 자동 수집됩니다: 다운로드 수는 [npm 공식 API](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin), Star 수는 [GitHub API](https://github.com/RevolutionLA/dsh-dream-skin/stargazers)에서 가져옵니다.*
@@ -383,3 +383,15 @@ dsh web # восстанавливает официальный вид
383
383
  - Справочник по архитектуре и API: официальный клиентский пакет DeepSeek Harness
384
384
  [ui-theme](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-theme).
385
385
  - Концептуальная дань: [Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin).
386
+
387
+ ---
388
+
389
+ ## 📈 Кривая роста
390
+
391
+ > Автоматически обновляется ежедневно (GitHub Actions). Левая ось: **суммарные скачивания** (бирюзовый); правая ось: **число звёзд** (фиолетовый) — сильно разные порядки величин, поэтому у каждого своя независимая ось Y.
392
+
393
+ <p align="center">
394
+ <img src="../../docs/stats.png?v=2" alt="Ежедневная кривая роста Star × суммарные скачивания dsh-dream-skin" width="900"/>
395
+ </p>
396
+
397
+ *Данные собираются каждые 24 часа: скачивания — из [официального API npm](https://api.npmjs.org/downloads/range/2026-08-15:2026-12-31/dsh-dream-skin), звёзды — из [API GitHub](https://github.com/RevolutionLA/dsh-dream-skin/stargazers).*
package/lib/client.js CHANGED
@@ -1022,8 +1022,59 @@ window.__ModuleLoader__.load({
1022
1022
  //#region dsh-dream-skin: wallpaper layer + token shading
1023
1023
  /** The fixed backdrop layer (z-index -1), created lazily. */
1024
1024
  let wallpaperEl = null;
1025
- /** Disposer for the current token-override layer. */
1026
- let wallpaperOverrideDispose = null;
1025
+ /**
1026
+ * Dynamic packages are assigned one token-override source by the production
1027
+ * client runner, regardless of the source label passed by the package. Keep
1028
+ * every Dream Skin contribution in one layer so wallpaper, popup opacity and
1029
+ * accent do not replace one another there.
1030
+ */
1031
+ const COMBINED_OVERRIDE_SOURCE = "dsh-dream-skin:appearance";
1032
+ let combinedOverrideDispose = null;
1033
+ let combinedOverrideApplying = false;
1034
+ let wallpaperTokenOverrides = {};
1035
+ let popupTokenOverrides = {};
1036
+ let accentTokenOverrides = {};
1037
+
1038
+ function rawActiveTheme(snapshot) {
1039
+ // `preference` is the authoritative selected theme id. In the production
1040
+ // dynamic-package event facade, `snapshot.active` can already be the
1041
+ // composed presentation object (and has been observed without a usable
1042
+ // third-party id). Looking it up by `active.id` then falls through to the
1043
+ // composed tokens and feeds our previous wallpaper wash back into the next
1044
+ // skin. Prefer the registered definition selected by `preference`; only use
1045
+ // active.id for `system`, where it resolves to the concrete light/dark theme.
1046
+ const savedId = readSavedSkin();
1047
+ const selectedId = typeof savedId === "string" && savedId !== DEFAULT_SKIN
1048
+ ? savedId
1049
+ : snapshot.preference === "system"
1050
+ ? snapshot.active?.id
1051
+ : snapshot.preference;
1052
+ return snapshot.themes?.find((theme) => theme.id === selectedId)
1053
+ || snapshot.themes?.find((theme) => theme.id === snapshot.active?.id)
1054
+ || snapshot.active;
1055
+ }
1056
+
1057
+ function applyCombinedTokenOverrides(ctx) {
1058
+ if (combinedOverrideApplying) return;
1059
+ combinedOverrideApplying = true;
1060
+ try {
1061
+ const overrides = {
1062
+ ...popupTokenOverrides,
1063
+ ...accentTokenOverrides,
1064
+ ...wallpaperTokenOverrides
1065
+ };
1066
+ if (Object.keys(overrides).length > 0) {
1067
+ const previousDispose = combinedOverrideDispose;
1068
+ combinedOverrideDispose = ctx.theme.overrideTokens(COMBINED_OVERRIDE_SOURCE, overrides);
1069
+ previousDispose?.();
1070
+ } else {
1071
+ combinedOverrideDispose?.();
1072
+ combinedOverrideDispose = null;
1073
+ }
1074
+ } finally {
1075
+ combinedOverrideApplying = false;
1076
+ }
1077
+ }
1027
1078
  /** The injected liquid-glass <style> node (leaf-card backdrop blur). */
1028
1079
  let materialStyleEl = null;
1029
1080
 
@@ -1059,12 +1110,12 @@ window.__ModuleLoader__.load({
1059
1110
  return resolveBase(scheme, active);
1060
1111
  }
1061
1112
 
1062
- /** Remove the wallpaper layer and its token overrides (fiber unload). */
1063
- function teardownWallpaper() {
1113
+ /** Remove the wallpaper layer and its token contribution. */
1114
+ function teardownWallpaper(ctx = null) {
1064
1115
  wallpaperEl?.remove();
1065
1116
  wallpaperEl = null;
1066
- wallpaperOverrideDispose?.();
1067
- wallpaperOverrideDispose = null;
1117
+ wallpaperTokenOverrides = {};
1118
+ if (ctx !== null) applyCombinedTokenOverrides(ctx);
1068
1119
  }
1069
1120
 
1070
1121
  //#region dsh-dream-skin: liquid-glass material CSS
@@ -1074,12 +1125,19 @@ window.__ModuleLoader__.load({
1074
1125
  * `--dsw-specific-input-major`) combined with `backdrop-filter: blur()`
1075
1126
  * so the diffused-glow wallpaper frosts through. Two safety rules, learned
1076
1127
  * from the earlier regression:
1077
- * 1. We only target LEAF cards (composer `.uV2eYG_card`, inline warning
1078
- * `.bqrRRG_card`, todo popover `.lXshSW_root`, dock `.lXshSW_root`/panel)
1079
- * — none of these are ancestors of a `position:fixed` dialog, so
1080
- * adding backdrop-filter here cannot trap a fixed modal (the earlier
1081
- * bug was blurring LARGE columns that contained the fixed settings
1082
- * modal).
1128
+ * 1. We only target LEAF cards that do NOT host a `position:fixed`
1129
+ * descendant. `backdrop-filter` (like `filter`/`transform`) turns
1130
+ * the element into a containing block, so a fixed-positioned child
1131
+ * is laid out relative to the card instead of the viewport. The
1132
+ * composer card (`.uV2eYG_card`) deliberately carries the stop /
1133
+ * send button Tooltips — fixed popovers — and blurring it made
1134
+ * them anchor to the card and spill to the bottom-right corner,
1135
+ * shoving the composer out of layout. So the material blur applies
1136
+ * to the inline-warning card (`.bqrRRG_card`) and the todo
1137
+ * popover/dock (`.lXshSW_root`, `._7yHdaG_panel`) but NOT to the
1138
+ * composer card; the composer keeps its translucent token fill.
1139
+ * (The earlier bug was blurring LARGE columns that contained the
1140
+ * fixed settings modal.)
1083
1141
  * 2. `@supports` guards browsers without backdrop-filter; if a hashed
1084
1142
  * class name changes in a future DSH the selectors no-op (blur just
1085
1143
  * stops) without ever breaking layout.
@@ -1091,7 +1149,7 @@ window.__ModuleLoader__.load({
1091
1149
  if (materialStyleEl !== null && document.head.contains(materialStyleEl)) return materialStyleEl;
1092
1150
  const parts = [
1093
1151
  "@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {",
1094
- " .uV2eYG_card, .bqrRRG_card {",
1152
+ " .bqrRRG_card {",
1095
1153
  " -webkit-backdrop-filter: blur(24px) saturate(150%);",
1096
1154
  " backdrop-filter: blur(24px) saturate(150%);",
1097
1155
  " }",
@@ -1220,12 +1278,11 @@ window.__ModuleLoader__.load({
1220
1278
  * Pure token override (no backdrop-filter), so it cannot re-trigger the
1221
1279
  * fixed-modal containing-block bug. Called at boot and on every slider move.
1222
1280
  */
1223
- const POPUP_OVERRIDE_SOURCE = "dsh-dream-skin:popup-opacity";
1224
1281
  const POPUP_TOKENS = ["--dsw-alias-bg-overlay", "--dsw-specific-menu"];
1225
- let popupOverrideDispose = null;
1226
1282
  function applyModalOverlay(ctx) {
1227
1283
  const alpha = readModalOpacity();
1228
- const active = ctx.theme.getTheme().active;
1284
+ const current = ctx.theme.getTheme();
1285
+ const active = rawActiveTheme(current);
1229
1286
  const baseFor = (scheme) => {
1230
1287
  if (active && active.colorScheme === scheme && typeof active.tokens["--dsw-alias-bg-base"] === "string") {
1231
1288
  return active.tokens["--dsw-alias-bg-base"];
@@ -1239,8 +1296,8 @@ window.__ModuleLoader__.load({
1239
1296
  dark: toRgba(baseFor("dark"), alpha)
1240
1297
  };
1241
1298
  }
1242
- popupOverrideDispose?.();
1243
- popupOverrideDispose = ctx.theme.overrideTokens(POPUP_OVERRIDE_SOURCE, overrides);
1299
+ popupTokenOverrides = overrides;
1300
+ applyCombinedTokenOverrides(ctx);
1244
1301
  }
1245
1302
  //#endregion
1246
1303
 
@@ -2498,8 +2555,6 @@ window.__ModuleLoader__.load({
2498
2555
  //#endregion
2499
2556
 
2500
2557
  //#region dsh-dream-skin: P0 accent override
2501
- /** Source identity for the per-user accent override layer. */
2502
- const ACCENT_OVERRIDE_SOURCE = "dsh-dream-skin:accent";
2503
2558
  /** Token names the accent override shades (brand + primary surfaces). */
2504
2559
  const ACCENT_TOKENS = [
2505
2560
  "--dsw-alias-brand-primary",
@@ -2507,8 +2562,6 @@ window.__ModuleLoader__.load({
2507
2562
  "--dsw-alias-button-primary-fill",
2508
2563
  "--dsw-alias-button-primary-dimmed"
2509
2564
  ];
2510
- /** Disposer for the active accent override layer. */
2511
- let accentOverrideDispose = null;
2512
2565
  /** Cached accent currently applied (hex or null). */
2513
2566
  let appliedAccent = null;
2514
2567
 
@@ -2528,16 +2581,16 @@ window.__ModuleLoader__.load({
2528
2581
  function applyAccent(ctx) {
2529
2582
  const accent = readAccent();
2530
2583
  if (accent === null) {
2531
- accentOverrideDispose?.();
2532
- accentOverrideDispose = null;
2584
+ accentTokenOverrides = {};
2585
+ applyCombinedTokenOverrides(ctx);
2533
2586
  appliedAccent = null;
2534
2587
  return null;
2535
2588
  }
2536
2589
  const pair = { light: accent, dark: accent };
2537
2590
  const overrides = {};
2538
2591
  for (const name of ACCENT_TOKENS) overrides[name] = pair;
2539
- accentOverrideDispose?.();
2540
- accentOverrideDispose = ctx.theme.overrideTokens(ACCENT_OVERRIDE_SOURCE, overrides);
2592
+ accentTokenOverrides = overrides;
2593
+ applyCombinedTokenOverrides(ctx);
2541
2594
  appliedAccent = accent;
2542
2595
  return accent;
2543
2596
  }
@@ -2550,8 +2603,6 @@ window.__ModuleLoader__.load({
2550
2603
  //#endregion
2551
2604
 
2552
2605
  //#region dsh-dream-skin: P0 wallpaper 2.0 (url / gradient / auto-dim)
2553
- /** Unique source identity for the wallpaper (already composited) layer. */
2554
- const WALLPAPER_OVERRIDE_SOURCE = "dsh-dream-skin:wallpaper";
2555
2606
  /** Read wallpaper kind (image|url|gradient). */
2556
2607
  function readWallpaperKind() {
2557
2608
  const kind = readStorage(WALLPAPER_KIND_KEY);
@@ -2634,7 +2685,7 @@ window.__ModuleLoader__.load({
2634
2685
  const bg = wallpaperBackgroundCss();
2635
2686
  const urlIsSet = bg !== null;
2636
2687
  if (!urlIsSet) {
2637
- teardownWallpaper();
2688
+ teardownWallpaper(ctx);
2638
2689
  return;
2639
2690
  }
2640
2691
  if (wallpaperEl === null || !document.body.contains(wallpaperEl)) {
@@ -2660,7 +2711,7 @@ window.__ModuleLoader__.load({
2660
2711
  // ThemeRuntime composes token override layers into snapshot.active. Reading
2661
2712
  // the active value here would feed our previous wallpaper wash back into the
2662
2713
  // next wash and hide the newly selected skin's raw base/sidebar colors.
2663
- const active = current.themes?.find((theme) => theme.id === current.active.id) || current.active;
2714
+ const active = rawActiveTheme(current);
2664
2715
  // When "link sidebar to main canvas" is on (default), the sidebar wash
2665
2716
  // uses the same alpha as the main canvas so the two halves don't look
2666
2717
  // split; when off, it uses the separately configured sidebar opacity.
@@ -2675,8 +2726,8 @@ window.__ModuleLoader__.load({
2675
2726
  dark: toRgba(resolveSidebar("dark", active), sidebarAlpha)
2676
2727
  }
2677
2728
  };
2678
- wallpaperOverrideDispose?.();
2679
- wallpaperOverrideDispose = ctx.theme.overrideTokens(WALLPAPER_OVERRIDE_SOURCE, overrides);
2729
+ wallpaperTokenOverrides = overrides;
2730
+ applyCombinedTokenOverrides(ctx);
2680
2731
  }
2681
2732
 
2682
2733
  // Wallpaper store bookkeeping lives at module scope so the module-level
@@ -2753,7 +2804,7 @@ window.__ModuleLoader__.load({
2753
2804
  writeStorage(WALLPAPER_URL_KEY, null);
2754
2805
  writeStorage(WALLPAPER_GRADIENT_KEY, null);
2755
2806
  writeStorage(WALLPAPER_KIND_KEY, null);
2756
- teardownWallpaper();
2807
+ teardownWallpaper(ctx);
2757
2808
  syncWallpaper();
2758
2809
  }
2759
2810
 
@@ -3151,8 +3202,11 @@ window.__ModuleLoader__.load({
3151
3202
  return;
3152
3203
  }
3153
3204
  if (current === "system" || current === "light" || current === "dark") {
3154
- ctx.theme.setTheme(savedSkin);
3155
3205
  skinRestoreCount += 1;
3206
+ // Count the attempt before setTheme(): ThemeRuntime publishes the
3207
+ // successful saved-skin theme/change synchronously, and that event
3208
+ // resets this consecutive-failure budget below.
3209
+ ctx.theme.setTheme(savedSkin);
3156
3210
  }
3157
3211
  };
3158
3212
  const scheduleSkinRestore = () => {
@@ -3233,7 +3287,16 @@ window.__ModuleLoader__.load({
3233
3287
  ctx.on("theme/change", (snapshot) => {
3234
3288
  const savedSkin = readSavedSkin();
3235
3289
  const builtIn = snapshot.preference === "system" || snapshot.preference === "light" || snapshot.preference === "dark";
3236
- if (typeof savedSkin === "string" && savedSkin !== DEFAULT_SKIN && builtIn) scheduleSkinRestore();
3290
+ if (typeof savedSkin === "string" && savedSkin !== DEFAULT_SKIN) {
3291
+ if (snapshot.preference === savedSkin) {
3292
+ // A successful restore ends the failure streak. Without this reset,
3293
+ // ordinary locale reloads consume the lifetime cap and the ninth
3294
+ // reload permanently falls back to Default (issue #36).
3295
+ skinRestoreCount = 0;
3296
+ } else if (builtIn) {
3297
+ scheduleSkinRestore();
3298
+ }
3299
+ }
3237
3300
  onBuiltinChange(snapshot);
3238
3301
  });
3239
3302
  settleBuiltin();
@@ -3254,8 +3317,11 @@ window.__ModuleLoader__.load({
3254
3317
  teardownWallpaper();
3255
3318
  teardownMaterial();
3256
3319
  disposeAllPacks();
3257
- popupOverrideDispose?.();
3258
- popupOverrideDispose = null;
3320
+ popupTokenOverrides = {};
3321
+ accentTokenOverrides = {};
3322
+ wallpaperTokenOverrides = {};
3323
+ combinedOverrideDispose?.();
3324
+ combinedOverrideDispose = null;
3259
3325
  }, "dsh-dream-skin: wallpaper + material + packs cleanup");
3260
3326
 
3261
3327
  const skinStore = createSkinStore();
@@ -3265,6 +3331,7 @@ window.__ModuleLoader__.load({
3265
3331
  // updates on every click — even if theme/change timing races or the host
3266
3332
  // revision doesn't bump as expected — so the selected card follows instantly.
3267
3333
  let skinRevision = 0;
3334
+ let wallpaperReshadeTimer = null;
3268
3335
  const syncSkinWith = (id) => {
3269
3336
  skinBound?.sync(id, ++skinRevision);
3270
3337
  // Do NOT re-shade the wallpaper here (issue #29): right after
@@ -3279,12 +3346,24 @@ window.__ModuleLoader__.load({
3279
3346
  };
3280
3347
  const syncSkin = (snapshot) => {
3281
3348
  skinBound?.sync(snapshot.preference, ++skinRevision);
3282
- // snapshot.active is the settled target skin here — safe to re-shade the
3283
- // wallpaper wash with the target skin's raw tokens. Pass the full snapshot
3284
- // so shadeTokens2 can ignore its own composed override layer.
3285
- if (wallpaperBackgroundCss() !== null) applyWallpaper2(ctx, snapshot);
3349
+ // Theme events are synchronous. Re-shading inside this listener publishes a
3350
+ // nested theme/change; a presenter registered after us can then apply the
3351
+ // outer (pre-shade) snapshot last, leaving the wallpaper one skin behind.
3352
+ // Run after the current event stack instead. Events emitted by our own
3353
+ // override happen while _applyingWallpaper is true and must not enqueue a
3354
+ // second pass.
3355
+ if (wallpaperBackgroundCss() !== null && !_applyingWallpaper) {
3356
+ if (wallpaperReshadeTimer !== null) clearTimeout(wallpaperReshadeTimer);
3357
+ wallpaperReshadeTimer = setTimeout(() => {
3358
+ wallpaperReshadeTimer = null;
3359
+ applyWallpaper2(ctx, ctx.theme.getTheme());
3360
+ }, 0);
3361
+ }
3286
3362
  };
3287
3363
  ctx.on("theme/change", syncSkin);
3364
+ ctx.effect(() => () => {
3365
+ if (wallpaperReshadeTimer !== null) clearTimeout(wallpaperReshadeTimer);
3366
+ }, "dsh-dream-skin: deferred wallpaper re-shade");
3288
3367
  // Keep the Accent row's base color (the active theme's brand color) in
3289
3368
  // sync when the skin/scheme changes — otherwise a row with no custom
3290
3369
  // accent keeps showing the PREVIOUS skin's brand color until remount.
@@ -3321,8 +3400,11 @@ window.__ModuleLoader__.load({
3321
3400
  syncSkin(ctx.theme.getTheme());
3322
3401
  return {
3323
3402
  setSkin: (id) => {
3324
- ctx.theme.setTheme(id);
3403
+ // Persist first: theme/change is synchronous. Wallpaper re-shading
3404
+ // must see the new selection (and Default must already have cleared
3405
+ // the old skin) while that event is being handled.
3325
3406
  writeSavedSkin(id);
3407
+ ctx.theme.setTheme(id);
3326
3408
  // Deterministically push the new preference into the slot store AND
3327
3409
  // re-shade the wallpaper so the selected card follows immediately,
3328
3410
  // independent of theme/change emission timing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-dream-skin",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "DeepSeek Harness 换肤插件(Dream Skin for DSH):8 套 iOS / Linear 式清透冷调的高质感主题 + 弥散光壁纸 + 每皮肤智能背景 + 强调色 + 主题包分享,把换肤做成材质与配色克制的高级感工艺,而非贴图 — in the spirit of Codex Dream Skin, elevated.",
5
5
  "license": "MIT",
6
6
  "type": "module",