@linxin666/dsh-pet 0.3.0 → 0.3.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAiiB5D"}
1
+ {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CA0iB5D"}
@@ -219,8 +219,9 @@ export function PetSprite(props) {
219
219
  // blank while the loop heat-up runs.
220
220
  const leadCol = track.frames[0];
221
221
  const lead = framePosition(cell, row, leadCol, scaleRef.current);
222
+ let lastPosStr = lead.x + 'px ' + lead.y + 'px';
222
223
  if (spriteRef.current !== null) {
223
- spriteRef.current.style.backgroundPosition = lead.x + 'px ' + lead.y + 'px';
224
+ spriteRef.current.style.backgroundPosition = lastPosStr;
224
225
  }
225
226
  if (reduceMotion)
226
227
  return;
@@ -237,8 +238,12 @@ export function PetSprite(props) {
237
238
  const currentTrack = trimTrack(tracks[current.animation], rows[currentRow] ?? tracks[current.animation].frames.length);
238
239
  const col = currentTrack.frames[current.frameIndex];
239
240
  const pos = framePosition(cell, currentRow, col, scaleRef.current);
240
- if (spriteRef.current !== null) {
241
- spriteRef.current.style.backgroundPosition = pos.x + 'px ' + pos.y + 'px';
241
+ const posStr = pos.x + 'px ' + pos.y + 'px';
242
+ if (posStr !== lastPosStr) {
243
+ lastPosStr = posStr;
244
+ if (spriteRef.current !== null) {
245
+ spriteRef.current.style.backgroundPosition = posStr;
246
+ }
242
247
  }
243
248
  raf = requestAnimationFrame(tick);
244
249
  return;
@@ -269,8 +274,12 @@ export function PetSprite(props) {
269
274
  }
270
275
  const col = track.frames[st.index];
271
276
  const pos = framePosition(cell, row, col, scaleRef.current);
272
- if (spriteRef.current !== null) {
273
- spriteRef.current.style.backgroundPosition = pos.x + 'px ' + pos.y + 'px';
277
+ const posStr = pos.x + 'px ' + pos.y + 'px';
278
+ if (posStr !== lastPosStr) {
279
+ lastPosStr = posStr;
280
+ if (spriteRef.current !== null) {
281
+ spriteRef.current.style.backgroundPosition = posStr;
282
+ }
274
283
  }
275
284
  raf = requestAnimationFrame(tick);
276
285
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAgEnI,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA0Q9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAgEnI,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoR9C"}
@@ -56,7 +56,14 @@ export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote
56
56
  * @param ctx - client root context.
57
57
  */
58
58
  export function apply(ctx) {
59
- ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'pet: dictionaries');
59
+ ctx.effect(() => {
60
+ try {
61
+ return ctx.locale.register(NS, { zh, en });
62
+ }
63
+ catch {
64
+ return () => { };
65
+ }
66
+ }, 'pet: dictionaries');
60
67
  // Built-in renderers dispatch through the plugin-wide registry (pet-center
61
68
  // M3). Registration is idempotent (id wins), so re-applies stay clean.
62
69
  defaultPetRendererRegistry.register(live2dRenderer);
@@ -78,18 +85,23 @@ export function apply(ctx) {
78
85
  // The section entry owns the controller: unregistering it (fiber disposal,
79
86
  // hot reload) releases the scope subscription through petSettings.dispose.
80
87
  ctx.slots.inject('settings.section', () => {
81
- const unregister = ctx.slots.register({
82
- name: 'settings.section',
83
- id: 'pet',
84
- order: 130,
85
- label: () => ctx.locale.bind('pet')('settings.title'),
86
- locale: 'pet',
87
- inject: () => petSettings.inject(),
88
- }, PetSettingsSection);
89
- return () => {
90
- unregister();
91
- petSettings.dispose();
92
- };
88
+ try {
89
+ const unregister = ctx.slots.register({
90
+ name: 'settings.section',
91
+ id: 'pet',
92
+ order: 130,
93
+ label: () => ctx.locale.bind('pet')('settings.title'),
94
+ locale: 'pet',
95
+ inject: () => petSettings.inject(),
96
+ }, PetSettingsSection);
97
+ return () => {
98
+ unregister();
99
+ petSettings.dispose();
100
+ };
101
+ }
102
+ catch {
103
+ return () => { };
104
+ }
93
105
  });
94
106
  // The global pet entry, its store, and the poll loop live while the plugin
95
107
  // is enabled; toggling the setting off hides the pet and stops polling.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-pet",
3
3
  "description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -576,6 +576,37 @@ describe('PetSprite definition-driven render', () => {
576
576
  act(() => { nextFrame?.(1_500) })
577
577
  expect(sprite.style.backgroundPosition).toBe('0px -960px')
578
578
  })
579
+
580
+ it('skips redundant backgroundPosition style assignments when frame coordinates do not change (issue #1013)', () => {
581
+ vi.spyOn(window, 'matchMedia').mockReturnValue({
582
+ matches: false,
583
+ media: '(prefers-reduced-motion: reduce)',
584
+ onchange: null,
585
+ addEventListener: () => {},
586
+ removeEventListener: () => {},
587
+ addListener: () => {},
588
+ removeListener: () => {},
589
+ dispatchEvent: () => false,
590
+ })
591
+ vi.spyOn(performance, 'now').mockReturnValue(0)
592
+ let nextFrame: FrameRequestCallback | undefined
593
+ vi.spyOn(window, 'requestAnimationFrame').mockImplementation(callback => {
594
+ nextFrame = callback
595
+ return 1
596
+ })
597
+ vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {})
598
+ renderPet({
599
+ snapshot: { ...snapshot, animation: 'idle', phase: 'idle' },
600
+ })
601
+ const sprite = screen.getByRole('button', { name: '鲸鱼娘' })
602
+ const styleSetterSpy = vi.spyOn(sprite.style, 'backgroundPosition', 'set')
603
+ // Idle frame 0 duration is 400ms. An intermediate tick at 16ms does not advance the frame.
604
+ act(() => { nextFrame?.(16) })
605
+ expect(styleSetterSpy).not.toHaveBeenCalled()
606
+ // A tick past 400ms advances to frame 1 and should set backgroundPosition once.
607
+ act(() => { nextFrame?.(410) })
608
+ expect(styleSetterSpy).toHaveBeenCalledTimes(1)
609
+ })
579
610
  })
580
611
 
581
612
  describe('PetSprite panel chrome from the voice pack (pet-center M4)', () => {
@@ -268,8 +268,9 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
268
268
  // blank while the loop heat-up runs.
269
269
  const leadCol = track.frames[0]!
270
270
  const lead = framePosition(cell, row, leadCol, scaleRef.current)
271
+ let lastPosStr = lead.x + 'px ' + lead.y + 'px'
271
272
  if (spriteRef.current !== null) {
272
- spriteRef.current.style.backgroundPosition = lead.x + 'px ' + lead.y + 'px'
273
+ spriteRef.current.style.backgroundPosition = lastPosStr
273
274
  }
274
275
  if (reduceMotion) return
275
276
  let raf = 0
@@ -288,8 +289,12 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
288
289
  )
289
290
  const col = currentTrack.frames[current.frameIndex]!
290
291
  const pos = framePosition(cell, currentRow, col, scaleRef.current)
291
- if (spriteRef.current !== null) {
292
- spriteRef.current.style.backgroundPosition = pos.x + 'px ' + pos.y + 'px'
292
+ const posStr = pos.x + 'px ' + pos.y + 'px'
293
+ if (posStr !== lastPosStr) {
294
+ lastPosStr = posStr
295
+ if (spriteRef.current !== null) {
296
+ spriteRef.current.style.backgroundPosition = posStr
297
+ }
293
298
  }
294
299
  raf = requestAnimationFrame(tick)
295
300
  return
@@ -319,8 +324,12 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
319
324
  }
320
325
  const col = track.frames[st.index]!
321
326
  const pos = framePosition(cell, row, col, scaleRef.current)
322
- if (spriteRef.current !== null) {
323
- spriteRef.current.style.backgroundPosition = pos.x + 'px ' + pos.y + 'px'
327
+ const posStr = pos.x + 'px ' + pos.y + 'px'
328
+ if (posStr !== lastPosStr) {
329
+ lastPosStr = posStr
330
+ if (spriteRef.current !== null) {
331
+ spriteRef.current.style.backgroundPosition = posStr
332
+ }
324
333
  }
325
334
  raf = requestAnimationFrame(tick)
326
335
  }
@@ -104,7 +104,13 @@ declare module '@deepseek-ai/cordis' {
104
104
  * @param ctx - client root context.
105
105
  */
106
106
  export function apply(ctx: ClientContext): void {
107
- ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'pet: dictionaries')
107
+ ctx.effect(() => {
108
+ try {
109
+ return ctx.locale.register(NS, { zh, en })
110
+ } catch {
111
+ return () => {}
112
+ }
113
+ }, 'pet: dictionaries')
108
114
 
109
115
  // Built-in renderers dispatch through the plugin-wide registry (pet-center
110
116
  // M3). Registration is idempotent (id wins), so re-applies stay clean.
@@ -129,17 +135,21 @@ export function apply(ctx: ClientContext): void {
129
135
  // The section entry owns the controller: unregistering it (fiber disposal,
130
136
  // hot reload) releases the scope subscription through petSettings.dispose.
131
137
  ctx.slots.inject('settings.section', () => {
132
- const unregister = ctx.slots.register({
133
- name: 'settings.section',
134
- id: 'pet',
135
- order: 130,
136
- label: () => ctx.locale.bind('pet')('settings.title'),
137
- locale: 'pet',
138
- inject: () => petSettings.inject(),
139
- }, PetSettingsSection)
140
- return () => {
141
- unregister()
142
- petSettings.dispose()
138
+ try {
139
+ const unregister = ctx.slots.register({
140
+ name: 'settings.section',
141
+ id: 'pet',
142
+ order: 130,
143
+ label: () => ctx.locale.bind('pet')('settings.title'),
144
+ locale: 'pet',
145
+ inject: () => petSettings.inject(),
146
+ }, PetSettingsSection)
147
+ return () => {
148
+ unregister()
149
+ petSettings.dispose()
150
+ }
151
+ } catch {
152
+ return () => {}
143
153
  }
144
154
  })
145
155
 
@@ -51,4 +51,13 @@ describe('pet hover panel css', () => {
51
51
  expect(bridge).toContain('top: 100%')
52
52
  expect(bridge).toContain('bottom: auto')
53
53
  })
54
+
55
+ it('isolates the pet float container and sprite on their own compositor layer (issue #1013)', () => {
56
+ const float = css.match(/\.float\s*\{([^}]*)\}/)?.[1] ?? ''
57
+ expect(float).toContain('will-change: transform')
58
+ expect(float).toContain('contain: layout style')
59
+
60
+ const sprite = css.match(/\.sprite\s*\{([^}]*)\}/)?.[1] ?? ''
61
+ expect(sprite).toContain('contain: paint')
62
+ })
54
63
  })
@@ -6,11 +6,14 @@
6
6
  display: flex;
7
7
  flex-direction: column;
8
8
  align-items: center;
9
+ will-change: transform;
10
+ contain: layout style;
9
11
  }
10
12
 
11
13
  .sprite {
12
14
  image-rendering: auto;
13
15
  touch-action: none;
16
+ contain: paint;
14
17
  }
15
18
 
16
19
  .spriteWrap {