dsh-background-by-model 0.7.2 → 0.7.4

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/lib/index.js CHANGED
@@ -59,15 +59,12 @@ const ROTATE_ORDERS = ["order", "shuffle"];
59
59
  const ROTATE_MIN_MS = 5e3;
60
60
  /** Slowest allowed dwell time (24 h): one image per day is still "rotation". */
61
61
  const ROTATE_MAX_MS = 864e5;
62
- /** Longest allowed cross-fade; 0 is a hard cut. */
63
- const ROTATE_FADE_MAX_MS = 3e3;
64
62
  /** Everything off: the shipped default, and what a config predating this feature gets. */
65
63
  const DEFAULT_ROTATION = {
66
64
  enabled: false,
67
65
  intervalMs: 6e4,
68
66
  order: "order",
69
- advanceOnSwitch: false,
70
- fadeMs: 320
67
+ advanceOnSwitch: false
71
68
  };
72
69
  /** A fresh rotation block (never hand out the shared default object). */
73
70
  function defaultRotation() {
@@ -124,6 +121,28 @@ const DEFAULT_PART_BLURS = {
124
121
  rightbar: 0,
125
122
  input: 0
126
123
  };
124
+ /** Longest allowed switch duration; 0 is a hard cut. */
125
+ const TRANSITION_MAX_MS = 3e3;
126
+ /** Every accepted `effect`, in UI order. */
127
+ const TRANSITION_EFFECTS = [
128
+ "fade",
129
+ "none",
130
+ "zoom",
131
+ "slide"
132
+ ];
133
+ /** Every accepted `easing`, in UI order. */
134
+ const TRANSITION_EASINGS = [
135
+ "ease",
136
+ "linear",
137
+ "ease-out",
138
+ "ease-in-out"
139
+ ];
140
+ /** The shipped default: the cross-fade this plugin already performed, at 320 ms. */
141
+ const DEFAULT_TRANSITION = {
142
+ effect: "fade",
143
+ easing: "ease",
144
+ durationMs: 320
145
+ };
127
146
  /**
128
147
  * `#RRGGBB` → the `[h, s, l]` triple every rule stores (`s` and `l` in 0..1), or
129
148
  * null when the string is not a hex color.
@@ -220,8 +239,7 @@ function normalizeRotation(raw) {
220
239
  enabled: r.enabled === true,
221
240
  intervalMs: clamp(r.intervalMs, ROTATE_MIN_MS, ROTATE_MAX_MS, d.intervalMs),
222
241
  order: ROTATE_ORDERS.includes(r.order) ? r.order : d.order,
223
- advanceOnSwitch: r.advanceOnSwitch === true,
224
- fadeMs: clamp(r.fadeMs, 0, ROTATE_FADE_MAX_MS, d.fadeMs)
242
+ advanceOnSwitch: r.advanceOnSwitch === true
225
243
  };
226
244
  }
227
245
  /** Own-property test for values that may be anything at all (a raw JSON entry). */
@@ -229,6 +247,54 @@ function hasOwn(o, key) {
229
247
  return Object.prototype.hasOwnProperty.call(o, key);
230
248
  }
231
249
  /**
250
+ * The duration a config written before the switch effect carries on any of its
251
+ * rules, or `undefined` when it carries none worth adopting.
252
+ *
253
+ * `rotate.fadeMs` was the per-rule duration from 0.7.0 on, and it had NO control
254
+ * anywhere — it could only be hand-edited, which is why the global setting
255
+ * replaced it. This is the one-shot lift that keeps such a hand-tuned value from
256
+ * being silently dropped on upgrade: the first rule whose value differs from the
257
+ * 320 ms every build shipped is adopted as the global duration. Values that are
258
+ * all the default are ignored, so an ordinary config keeps the ordinary default,
259
+ * and rules that DISAGREE cannot be represented by one global number — the first
260
+ * deliberate value wins, and the others are gone with the field they lived on.
261
+ */
262
+ function legacyFadeMs(rawRules) {
263
+ if (!Array.isArray(rawRules)) return void 0;
264
+ for (const entry of rawRules) {
265
+ const rotate = entry?.rotate;
266
+ if (rotate === null || typeof rotate !== "object") continue;
267
+ const value = rotate.fadeMs;
268
+ if (typeof value !== "number" || !isFinite(value)) continue;
269
+ if (value === DEFAULT_TRANSITION.durationMs) continue;
270
+ return clamp(value, 0, TRANSITION_MAX_MS, DEFAULT_TRANSITION.durationMs);
271
+ }
272
+ }
273
+ /**
274
+ * Coerce one persisted transition block; a config written before this setting
275
+ * existed has no `transition` key at all and gets the shipped default, which is
276
+ * the behaviour it already had.
277
+ *
278
+ * `legacyMs` is the per-rule `fadeMs` this shape replaced (see `legacyFadeMs`),
279
+ * adopted only when the incoming config has no `durationMs` of its own — so a
280
+ * deliberate hand-tuned blend survives the upgrade instead of being dropped with
281
+ * the field it used to live on.
282
+ *
283
+ * `durationMs` is clamped with `0` kept as a real value (a hard cut) rather than
284
+ * read as "absent": 0 is exactly how a hard cut is asked for without changing
285
+ * the effect.
286
+ */
287
+ function normalizeTransition(raw, legacyMs) {
288
+ const t = raw ?? {};
289
+ const d = DEFAULT_TRANSITION;
290
+ const fallback = typeof legacyMs === "number" && isFinite(legacyMs) ? legacyMs : d.durationMs;
291
+ return {
292
+ effect: TRANSITION_EFFECTS.includes(t.effect) ? t.effect : d.effect,
293
+ easing: TRANSITION_EASINGS.includes(t.easing) ? t.easing : d.easing,
294
+ durationMs: clamp(t.durationMs, 0, TRANSITION_MAX_MS, fallback)
295
+ };
296
+ }
297
+ /**
232
298
  * Coerce one persisted image entry, or null when it lacks a usable slot.
233
299
  *
234
300
  * `inherit` is the RULE's own color, and it is applied only to an entry that has
@@ -365,6 +431,7 @@ function normalizeConfig(raw) {
365
431
  rules,
366
432
  opacities,
367
433
  blurs,
434
+ transition: normalizeTransition(r.transition, legacyFadeMs(r.rules)),
368
435
  settingsOpacity: clamp01(r.settingsOpacity, 1),
369
436
  chatTextOpacity: clamp01(r.chatTextOpacity, 0),
370
437
  trajectoryOpacity: clamp01(r.trajectoryOpacity, 1),
@@ -498,6 +565,7 @@ const LEGACY_CONFIG_KEYS = /* @__PURE__ */ new Set([
498
565
  "bgState"
499
566
  ]);
500
567
  const LEGACY_RULE_KEYS = /* @__PURE__ */ new Set(["slot", "bgState"]);
568
+ const LEGACY_NESTED_KEYS = /* @__PURE__ */ new Set(["rules[].rotate.fadeMs"]);
501
569
  const warnedConfigKeys = /* @__PURE__ */ new Set();
502
570
  function warnUnknownConfigKeys(raw, normalized) {
503
571
  if (raw === null || typeof raw !== "object") return;
@@ -516,6 +584,16 @@ function warnUnknownConfigKeys(raw, normalized) {
516
584
  const keys = new Set(Object.keys(have));
517
585
  for (const key of Object.keys(got)) if (!keys.has(key)) warned(`${group}.${key}`);
518
586
  }
587
+ const shapes = [[
588
+ "transition",
589
+ r.transition,
590
+ normalized.transition
591
+ ]];
592
+ for (const [label, got, known] of shapes) {
593
+ if (got === null || typeof got !== "object") continue;
594
+ const keys = new Set(Object.keys(known));
595
+ for (const key of Object.keys(got)) if (!keys.has(key)) warned(`${label}.${key}`);
596
+ }
519
597
  const sent = Array.isArray(r.rules) ? r.rules[0] : void 0;
520
598
  const have = normalized.rules[0];
521
599
  if (sent !== null && typeof sent === "object" && have !== void 0) {
@@ -534,7 +612,7 @@ function warnUnknownConfigKeys(raw, normalized) {
534
612
  for (const [label, got, known] of nested) {
535
613
  if (got === null || typeof got !== "object" || known === null || known === void 0) continue;
536
614
  const knownKeys = new Set(Object.keys(known));
537
- for (const key of Object.keys(got)) if (!knownKeys.has(key)) warned(`${label}.${key}`);
615
+ for (const key of Object.keys(got)) if (!knownKeys.has(key) && !LEGACY_NESTED_KEYS.has(`${label}.${key}`)) warned(`${label}.${key}`);
538
616
  }
539
617
  }
540
618
  }
@@ -740,7 +818,7 @@ async function handleRpcMethod(ctx, endpoint, payload) {
740
818
  value: {
741
819
  config: await readConfig(),
742
820
  slots: await listSlots(),
743
- schema: 4
821
+ schema: 5
744
822
  }
745
823
  };
746
824
  case "writeConfig": return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-background-by-model",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpapers — every image with its own framing and theme color — plus layout mode, opacity and blur; the background follows the model you switch to, and a rule can rotate through several images on its own rhythm.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -58,10 +58,11 @@
58
58
  "typecheck": "tsc -p tsconfig.json",
59
59
  "check:holiday": "node scripts/holiday-check.ts",
60
60
  "check:rotation": "node scripts/rotation-check.ts",
61
+ "check:transition": "node scripts/transition-check.ts",
61
62
  "check:repaint": "node scripts/repaint-check.ts",
62
63
  "check:ui": "node scripts/ui-strings-check.ts",
63
64
  "check:node": "node scripts/node-half-check.mjs",
64
- "test": "tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/repaint-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs"
65
+ "test": "tsdown && tsc -p tsconfig.json && node scripts/holiday-check.ts && node scripts/rotation-check.ts && node scripts/transition-check.ts && node scripts/repaint-check.ts && node scripts/ui-strings-check.ts && node scripts/node-half-check.mjs"
65
66
  },
66
67
  "devDependencies": {
67
68
  "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",