@lemoncat7/dsh-theme-xiaohei 0.4.3 → 0.4.5

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.
Files changed (51) hide show
  1. package/docs/releases/0.4.4.md +7 -0
  2. package/docs/releases/0.4.5.md +21 -0
  3. package/docs/startup-audit-20261013.md +47 -0
  4. package/lib/chrome/conversation-messages.js +17 -4
  5. package/lib/chrome/conversation-submissions.d.ts +3 -0
  6. package/lib/chrome/conversation-submissions.js +68 -0
  7. package/lib/chrome/sidebar.d.ts +1 -1
  8. package/lib/chrome/sidebar.js +20 -0
  9. package/lib/chrome.js +2 -0
  10. package/lib/client.decorations.js +51 -0
  11. package/lib/client.js +134 -750
  12. package/lib/client.pose-chin-dark.js +4 -0
  13. package/lib/client.pose-chin-light.js +4 -0
  14. package/lib/client.pose-doze-dark.js +4 -0
  15. package/lib/client.pose-doze-light.js +4 -0
  16. package/lib/client.pose-halfpeek-dark.js +4 -0
  17. package/lib/client.pose-halfpeek-light.js +4 -0
  18. package/lib/client.pose-peek-dark.js +4 -0
  19. package/lib/client.pose-peek-light.js +4 -0
  20. package/lib/client.pose-seated-dark.js +4 -0
  21. package/lib/client.pose-seated-light.js +4 -0
  22. package/lib/client.rig-chin-dark.js +4 -0
  23. package/lib/client.rig-chin-light.js +4 -0
  24. package/lib/client.rig-halfpeek-dark.js +4 -0
  25. package/lib/client.rig-halfpeek-light.js +4 -0
  26. package/lib/client.rig-peek-dark.js +4 -0
  27. package/lib/client.rig-peek-light.js +4 -0
  28. package/lib/client.rig-seated-dark.js +4 -0
  29. package/lib/client.rig-seated-light.js +4 -0
  30. package/lib/composer-draft-preview.d.ts +4 -0
  31. package/lib/composer-draft-preview.js +8 -0
  32. package/lib/decorations-entry.d.ts +3 -0
  33. package/lib/decorations-entry.js +18 -0
  34. package/lib/deferred-decorations.d.ts +3 -0
  35. package/lib/deferred-decorations.js +51 -0
  36. package/lib/plugin.js +11 -4
  37. package/lib/portal.js +1 -1
  38. package/lib/scene/character-assets.d.ts +13 -0
  39. package/lib/scene/character-assets.js +26 -0
  40. package/lib/scene/character-motion.js +14 -7
  41. package/lib/scene/ids.d.ts +5 -0
  42. package/lib/scene/ids.js +5 -0
  43. package/lib/scene/runtime.d.ts +1 -1
  44. package/lib/scene/runtime.js +2 -15
  45. package/lib/scene/styles.d.ts +1 -3
  46. package/lib/scene/styles.js +2 -8
  47. package/lib/scene/wallpaper-character-styles.js +1 -5
  48. package/lib/scene/wallpaper-character.d.ts +2 -1
  49. package/lib/scene/wallpaper-character.js +24 -15
  50. package/lib/sidebar-glass.js +1 -1
  51. package/package.json +4 -1
@@ -1,4 +1,4 @@
1
- import { CHARACTER_MOTION } from '../generated-character-motion.js';
1
+ import { characterAssets } from './character-assets.js';
2
2
  import { createCharacterMotionClock } from './character-motion-clock.js';
3
3
  import { createRigRenderer } from './character-rig-renderer.js';
4
4
  import { EYE_RIGS } from './character-eye-rig.js';
@@ -10,6 +10,7 @@ export function createCharacterMotion(doc) {
10
10
  let disposed = false, part, rig;
11
11
  let canvas, render;
12
12
  let pose = '', appearance = '', revision = 0, nearby = false, lastPointer = 0;
13
+ let selection = '';
13
14
  let attentionTimer, lastAttention = -Infinity;
14
15
  let gazePoint = [0, 0], activeAction, activeProgress = 0;
15
16
  let bounds;
@@ -46,9 +47,10 @@ export function createCharacterMotion(doc) {
46
47
  return;
47
48
  const mode = doc.documentElement.dataset.xiaoheiAppearance === 'light' ? 'light' : 'dark';
48
49
  const moving = doc.documentElement.hasAttribute('data-xiaohei-sidebar-resizing');
49
- const next = !doc.hidden && !reduced.matches && !moving && part ? CHARACTER_MOTION[pose]?.[mode] : undefined;
50
- if (next === rig && appearance === mode)
50
+ const key = !doc.hidden && !reduced.matches && !moving && part ? `${pose}-${mode}` : '';
51
+ if (key === selection && appearance === mode)
51
52
  return;
53
+ selection = key;
52
54
  cancelAttention();
53
55
  render = undefined;
54
56
  clock.setClips(undefined);
@@ -61,15 +63,19 @@ export function createCharacterMotion(doc) {
61
63
  canvas = undefined;
62
64
  if (part)
63
65
  part.dataset.motion = 'rest';
64
- rig = next;
66
+ rig = undefined;
65
67
  appearance = mode;
66
68
  nearby = false;
67
- if (!next || !part)
69
+ if (!key || !part)
68
70
  return;
69
71
  const currentPart = part;
70
- void Promise.all(Object.values(next.layers).map(layer => decode(layer.src))).then(() => {
72
+ void characterAssets.rig(pose, mode).then(async (next) => {
73
+ if (!next || disposed || current !== revision)
74
+ return;
75
+ await Promise.all(Object.values(next.layers).map(layer => decode(layer.src)));
71
76
  if (disposed || current !== revision || Object.values(next.layers).some(layer => !images.get(layer.src)))
72
77
  return;
78
+ rig = next;
73
79
  canvas = doc.createElement('canvas');
74
80
  canvas.className = 'xiaohei-character-motion';
75
81
  canvas.width = next.size[0];
@@ -87,7 +93,7 @@ export function createCharacterMotion(doc) {
87
93
  ...(next.layers.tail ? { tail: { duration: 2400 } } : {}),
88
94
  attention: { duration: 2400 },
89
95
  });
90
- });
96
+ }).catch(() => { });
91
97
  }
92
98
  const pointer = (event) => {
93
99
  if (!part || !render || event.pointerType !== 'mouse')
@@ -141,6 +147,7 @@ export function createCharacterMotion(doc) {
141
147
  if (nextPart !== part || pose !== nextPose) {
142
148
  cancelAttention();
143
149
  revision++;
150
+ selection = '';
144
151
  render = undefined;
145
152
  clock.setClips(undefined);
146
153
  gaze.reset();
@@ -0,0 +1,5 @@
1
+ /** Asset-free identifiers shared by the startup shell and deferred scenery. */
2
+ export declare const XIAOHEI_SCENE_STYLE_ID = "dsh-theme-xiaohei/scene-style";
3
+ export declare const XIAOHEI_SCENE_LAYER_ID = "dsh-theme-xiaohei/scene-layer";
4
+ export declare const XIAOHEI_SCENE_WORLD_CLASS = "xiaohei-scene__world";
5
+ //# sourceMappingURL=ids.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** Asset-free identifiers shared by the startup shell and deferred scenery. */
2
+ export const XIAOHEI_SCENE_STYLE_ID = 'dsh-theme-xiaohei/scene-style';
3
+ export const XIAOHEI_SCENE_LAYER_ID = 'dsh-theme-xiaohei/scene-layer';
4
+ export const XIAOHEI_SCENE_WORLD_CLASS = 'xiaohei-scene__world';
5
+ //# sourceMappingURL=ids.js.map
@@ -4,7 +4,7 @@ export declare function configureXiaoheiWorldRenderer(renderer: XiaoheiWorldRend
4
4
  /** Number of top-level decorative parts installed into the ambient layer. */
5
5
  export declare const XIAOHEI_SCENE_PART_COUNT: 1;
6
6
  /**
7
- * Install the static wallpaper after plugin boot becomes idle. Appearance is
7
+ * Install the small static wallpaper immediately with the theme. Appearance is
8
8
  * CSS-driven; no image load handlers or per-frame character animation.
9
9
  */
10
10
  export declare function installXiaoheiScene(doc?: Document | undefined): () => void;
@@ -14,7 +14,7 @@ const PARTS = [
14
14
  /** Number of top-level decorative parts installed into the ambient layer. */
15
15
  export const XIAOHEI_SCENE_PART_COUNT = PARTS.length;
16
16
  /**
17
- * Install the static wallpaper after plugin boot becomes idle. Appearance is
17
+ * Install the small static wallpaper immediately with the theme. Appearance is
18
18
  * CSS-driven; no image load handlers or per-frame character animation.
19
19
  */
20
20
  export function installXiaoheiScene(doc = typeof document === 'undefined' ? undefined : document) {
@@ -60,22 +60,9 @@ export function installXiaoheiScene(doc = typeof document === 'undefined' ? unde
60
60
  style.remove();
61
61
  };
62
62
  };
63
- const win = doc.defaultView;
64
- let cancelSchedule = () => { };
65
- if (win !== null && typeof win.requestIdleCallback === 'function') {
66
- const idleId = win.requestIdleCallback(mount, { timeout: 800 });
67
- cancelSchedule = () => win.cancelIdleCallback(idleId);
68
- }
69
- else if (win !== null) {
70
- const timeoutId = win.setTimeout(mount, 0);
71
- cancelSchedule = () => win.clearTimeout(timeoutId);
72
- }
73
- else {
74
- mount();
75
- }
63
+ mount();
76
64
  return () => {
77
65
  disposed = true;
78
- cancelSchedule();
79
66
  removeMountedScene();
80
67
  };
81
68
  }
@@ -1,7 +1,5 @@
1
+ export { XIAOHEI_SCENE_STYLE_ID, XIAOHEI_SCENE_LAYER_ID, XIAOHEI_SCENE_WORLD_CLASS } from './ids.js';
1
2
  /** Stable scene and material ids, shared with lifecycle and browser checks. */
2
- export declare const XIAOHEI_SCENE_STYLE_ID = "dsh-theme-xiaohei/scene-style";
3
- export declare const XIAOHEI_SCENE_LAYER_ID = "dsh-theme-xiaohei/scene-layer";
4
- export declare const XIAOHEI_SCENE_WORLD_CLASS = "xiaohei-scene__world";
5
3
  /** Static paired wallpapers; no per-frame effects or session-state dependency. */
6
4
  export declare const XIAOHEI_SCENE_CSS: string;
7
5
  //# sourceMappingURL=styles.d.ts.map
@@ -1,9 +1,7 @@
1
1
  import { XIAOHEI_WALLPAPER_LIGHT, XIAOHEI_WALLPAPER_DARK, } from '../generated-backgrounds.js';
2
- import { XIAOHEI_WALLPAPER_CHARACTER_CSS } from './wallpaper-character-styles.js';
2
+ import { XIAOHEI_SCENE_LAYER_ID, XIAOHEI_SCENE_WORLD_CLASS } from './ids.js';
3
+ export { XIAOHEI_SCENE_STYLE_ID, XIAOHEI_SCENE_LAYER_ID, XIAOHEI_SCENE_WORLD_CLASS } from './ids.js';
3
4
  /** Stable scene and material ids, shared with lifecycle and browser checks. */
4
- export const XIAOHEI_SCENE_STYLE_ID = 'dsh-theme-xiaohei/scene-style';
5
- export const XIAOHEI_SCENE_LAYER_ID = 'dsh-theme-xiaohei/scene-layer';
6
- export const XIAOHEI_SCENE_WORLD_CLASS = 'xiaohei-scene__world';
7
5
  /** Static paired wallpapers; no per-frame effects or session-state dependency. */
8
6
  export const XIAOHEI_SCENE_CSS = `
9
7
  body {
@@ -57,10 +55,6 @@ html[data-xiaohei-appearance='light'] .${XIAOHEI_SCENE_WORLD_CLASS} {
57
55
  --xiaohei-wallpaper-wide: url("${XIAOHEI_WALLPAPER_LIGHT}");
58
56
  }
59
57
 
60
- /* The atmosphere contains no character; responsive poses are placed separately
61
- * against measured free space, never by cropping a portrait into messages. */
62
- ${XIAOHEI_WALLPAPER_CHARACTER_CSS}
63
-
64
58
  .${XIAOHEI_SCENE_WORLD_CLASS} > * {
65
59
  width: 100%;
66
60
  height: 100%;
@@ -1,8 +1,7 @@
1
- import { CHARACTER_POSES, CHARACTER_POSE_NAMES } from './character-poses.js';
1
+ import { CHARACTER_POSE_NAMES } from './character-assets.js';
2
2
  /** Complete raster poses remain crisp; only entry/exit opacity and transform move. */
3
3
  export const XIAOHEI_WALLPAPER_CHARACTER_CSS = `
4
4
  .xiaohei-wallpaper-character {
5
- ${CHARACTER_POSE_NAMES.map(pose => `--xiaohei-${pose}-art: url("${CHARACTER_POSES[pose].dark}");`).join('\n ')}
6
5
  position: absolute;
7
6
  inset: 0;
8
7
  pointer-events: none;
@@ -11,9 +10,6 @@ export const XIAOHEI_WALLPAPER_CHARACTER_CSS = `
11
10
  html[data-xiaohei-sidebar-resizing] .xiaohei-wallpaper-character {
12
11
  visibility: hidden;
13
12
  }
14
- html[data-xiaohei-appearance='light'] .xiaohei-wallpaper-character {
15
- ${CHARACTER_POSE_NAMES.map(pose => `--xiaohei-${pose}-art: url("${CHARACTER_POSES[pose].light}");`).join('\n ')}
16
- }
17
13
  .xiaohei-wallpaper-character > span {
18
14
  position: absolute;
19
15
  display: block;
@@ -1,4 +1,5 @@
1
+ import { type CharacterPose, type CharacterAppearance } from './character-assets.js';
1
2
  export declare const XIAOHEI_WALLPAPER_CHARACTER_ID = "dsh-theme-xiaohei/wallpaper-character";
2
3
  /** Places the layered character only when the host geometry is settled. */
3
- export declare function installXiaoheiWallpaperCharacter(doc?: Document | undefined): () => void;
4
+ export declare function installXiaoheiWallpaperCharacter(doc?: Document | undefined, loadPose?: (pose: CharacterPose, appearance: CharacterAppearance) => Promise<string>): () => void;
4
5
  //# sourceMappingURL=wallpaper-character.d.ts.map
@@ -1,13 +1,13 @@
1
1
  import { subscribeXiaoheiHostDom } from '../host-dom.js';
2
2
  import { XIAOHEI_HOST_SELECTORS } from '../host-contract.js';
3
- import { XIAOHEI_SCENE_LAYER_ID } from './styles.js';
3
+ import { XIAOHEI_SCENE_LAYER_ID } from './ids.js';
4
4
  import { resolveCharacterPlacement } from './character-layout.js';
5
5
  import { createCharacterIdleController } from './character-idle.js';
6
- import { CHARACTER_POSES, CHARACTER_POSE_NAMES } from './character-poses.js';
6
+ import { characterAssets, CHARACTER_POSE_NAMES } from './character-assets.js';
7
7
  import { createCharacterMotion } from './character-motion.js';
8
8
  export const XIAOHEI_WALLPAPER_CHARACTER_ID = 'dsh-theme-xiaohei/wallpaper-character';
9
9
  /** Places the layered character only when the host geometry is settled. */
10
- export function installXiaoheiWallpaperCharacter(doc = typeof document === 'undefined' ? undefined : document) {
10
+ export function installXiaoheiWallpaperCharacter(doc = typeof document === 'undefined' ? undefined : document, loadPose = characterAssets.pose) {
11
11
  const win = doc?.defaultView;
12
12
  if (!doc || !win)
13
13
  return () => { };
@@ -18,7 +18,7 @@ export function installXiaoheiWallpaperCharacter(doc = typeof document === 'unde
18
18
  let targets = [];
19
19
  let dirty = true;
20
20
  let placement = { pose: 'hidden', left: 0, top: 0, width: 0, height: 0 };
21
- const ready = new Set();
21
+ const ready = new Map();
22
22
  const loading = new Set();
23
23
  const failed = new Set();
24
24
  const motion = typeof win.matchMedia === 'function' ? createCharacterMotion(doc) : undefined;
@@ -132,33 +132,39 @@ export function installXiaoheiWallpaperCharacter(doc = typeof document === 'unde
132
132
  motion?.setPose(undefined, 'hidden');
133
133
  return;
134
134
  }
135
- if (!ready.has(pose)) {
135
+ const appearance = doc.documentElement.dataset.xiaoheiAppearance === 'light' ? 'light' : 'dark';
136
+ const key = `${pose}-${appearance}`;
137
+ if (!ready.has(key)) {
136
138
  motion?.setPose(undefined, 'hidden');
137
139
  // Do not retain the old location across a layout change: it could now
138
140
  // overlap a message. Idle changes keep the already decoded seated pose.
139
141
  if (placement.pose !== 'seated' || !['seated', 'chin', 'doze'].includes(host.dataset.pose ?? ''))
140
142
  host.dataset.pose = 'hidden';
141
- if (!loading.has(pose) && !failed.has(pose)) {
142
- loading.add(pose);
143
- // Decode only a requested pose, in both appearances so changing theme
144
- // cannot produce a blank frame. No image fetch or perpetual RAF loop.
145
- void Promise.all(Object.values(CHARACTER_POSES[pose]).map(src => {
143
+ if (!loading.has(key) && !failed.has(key)) {
144
+ loading.add(key);
145
+ // Fetch only the requested pose and palette, not the complete catalog.
146
+ void loadPose(pose, appearance).then(async (src) => {
147
+ if (disposed)
148
+ return;
146
149
  const image = new win.Image();
147
150
  image.src = src;
148
- return image.decode();
149
- })).then(() => {
151
+ await image.decode();
150
152
  if (disposed)
151
153
  return;
152
- loading.delete(pose);
153
- ready.add(pose);
154
+ loading.delete(key);
155
+ ready.set(key, src);
154
156
  schedule();
155
- }, () => { loading.delete(pose); failed.add(pose); });
157
+ }).catch(() => { loading.delete(key); failed.add(key); });
156
158
  }
157
159
  return;
158
160
  }
159
161
  // Each pose keeps its own coordinates during the short crossfade. No
160
162
  // travelling from the right edge through the conversation to the sidebar.
161
163
  const part = host.querySelector(`.xiaohei-wallpaper-character__${pose}`);
164
+ if (part.dataset.appearance !== appearance) {
165
+ part.style.setProperty(`--xiaohei-${pose}-art`, `url("${ready.get(key)}")`);
166
+ part.dataset.appearance = appearance;
167
+ }
162
168
  for (const key of ['left', 'top', 'width', 'height']) {
163
169
  const value = `${Math.round(placement[key] * 100) / 100}px`;
164
170
  if (part.style[key] !== value)
@@ -183,6 +189,8 @@ export function installXiaoheiWallpaperCharacter(doc = typeof document === 'unde
183
189
  settled = win.setTimeout(() => { settled = undefined; dirty = true; schedule(); }, 240);
184
190
  }
185
191
  const unsubscribe = subscribeXiaoheiHostDom(doc, schedule);
192
+ const appearanceObserver = new win.MutationObserver(schedule);
193
+ appearanceObserver.observe(doc.documentElement, { attributes: true, attributeFilter: ['data-xiaohei-appearance'] });
186
194
  const resized = () => { dirty = true; schedule(); scheduleSettled(); };
187
195
  win.addEventListener('resize', resized, { passive: true });
188
196
  const activity = () => idle.activity();
@@ -201,6 +209,7 @@ export function installXiaoheiWallpaperCharacter(doc = typeof document === 'unde
201
209
  win.clearTimeout(settled);
202
210
  unsubscribe();
203
211
  observer?.disconnect();
212
+ appearanceObserver.disconnect();
204
213
  idle.dispose();
205
214
  motion?.dispose();
206
215
  for (const event of events)
@@ -1,4 +1,4 @@
1
- import { XIAOHEI_SCENE_LAYER_ID, XIAOHEI_SCENE_WORLD_CLASS } from './scene.js';
1
+ import { XIAOHEI_SCENE_LAYER_ID, XIAOHEI_SCENE_WORLD_CLASS } from './scene/ids.js';
2
2
  import { subscribeXiaoheiHostDom } from './host-dom.js';
3
3
  import { XIAOHEI_HOST_SELECTORS } from './host-contract.js';
4
4
  import { createSidebarReveal } from './sidebar-reveal.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemoncat7/dsh-theme-xiaohei",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "A Luo Xiaohei-inspired animated dark theme for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -26,6 +26,9 @@
26
26
  "./package.json": "./package.json"
27
27
  },
28
28
  "files": [
29
+ "docs/releases/0.4.5.md",
30
+ "docs/startup-audit-20261013.md",
31
+ "docs/releases/0.4.4.md",
29
32
  "docs/releases/0.4.3.md",
30
33
  "lib/**/*.js",
31
34
  "lib/**/*.d.ts",