@linxin666/dsh-pet 0.3.24 → 0.3.25

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 (72) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -3
  3. package/README.zh.md +4 -3
  4. package/icon.svg +3 -0
  5. package/lib/client.js +522 -194
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +706 -422
  8. package/lib/live2d-vendor.js +96 -96
  9. package/lib/live2d-vendor.js.map +1 -1
  10. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  11. package/lib/types/client/PetDockEntry.js +1 -1
  12. package/lib/types/client/PetSettingsCard.d.ts +20 -9
  13. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/PetSettingsCard.js +116 -5
  15. package/lib/types/client/PetSprite.d.ts +6 -0
  16. package/lib/types/client/PetSprite.d.ts.map +1 -1
  17. package/lib/types/client/PetSprite.js +95 -0
  18. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  19. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  20. package/lib/types/client/PluginSettingsCard.js +3 -2
  21. package/lib/types/client/gameplay-hud.d.ts +10 -1
  22. package/lib/types/client/gameplay-hud.d.ts.map +1 -1
  23. package/lib/types/client/gameplay-hud.js +137 -23
  24. package/lib/types/client/index.d.ts +8 -5
  25. package/lib/types/client/index.d.ts.map +1 -1
  26. package/lib/types/client/index.js +61 -27
  27. package/lib/types/client/main-session.d.ts +33 -0
  28. package/lib/types/client/main-session.d.ts.map +1 -0
  29. package/lib/types/client/main-session.js +34 -0
  30. package/lib/types/client/settings-form.d.ts +17 -17
  31. package/lib/types/client/settings-form.d.ts.map +1 -1
  32. package/lib/types/client/settings-form.js +30 -27
  33. package/lib/types/event-projection.d.ts.map +1 -1
  34. package/lib/types/event-projection.js +5 -3
  35. package/lib/types/gameplay.d.ts +79 -5
  36. package/lib/types/gameplay.d.ts.map +1 -1
  37. package/lib/types/gameplay.js +212 -17
  38. package/lib/types/http.d.ts +18 -1
  39. package/lib/types/http.d.ts.map +1 -1
  40. package/lib/types/http.js +22 -1
  41. package/lib/types/index.d.ts +100 -29
  42. package/lib/types/index.d.ts.map +1 -1
  43. package/lib/types/index.js +113 -64
  44. package/lib/types/persist.d.ts.map +1 -1
  45. package/lib/types/persist.js +3 -1
  46. package/lib/types/routes.d.ts.map +1 -1
  47. package/lib/types/routes.js +3 -1
  48. package/lib/types/service.d.ts +8 -3
  49. package/lib/types/service.d.ts.map +1 -1
  50. package/lib/types/service.js +26 -4
  51. package/package.json +46 -26
  52. package/src/client/PetDockEntry.tsx +1 -0
  53. package/src/client/PetSettingsCard.test.tsx +103 -0
  54. package/src/client/PetSettingsCard.tsx +129 -26
  55. package/src/client/PetSprite.tsx +96 -0
  56. package/src/client/PluginSettingsCard.tsx +7 -2
  57. package/src/client/gameplay-hud.test.tsx +106 -8
  58. package/src/client/gameplay-hud.tsx +143 -28
  59. package/src/client/index.test.tsx +37 -4
  60. package/src/client/index.ts +74 -34
  61. package/src/client/main-session.ts +43 -0
  62. package/src/client/settings-form.ts +34 -31
  63. package/src/event-projection.ts +5 -3
  64. package/src/gameplay.test.ts +138 -1
  65. package/src/gameplay.ts +253 -16
  66. package/src/http.ts +23 -1
  67. package/src/index.test.ts +107 -11
  68. package/src/index.ts +162 -73
  69. package/src/persist.ts +3 -1
  70. package/src/registry.test.ts +3 -2
  71. package/src/routes.ts +3 -1
  72. package/src/service.ts +27 -5
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  * @module @linxin666/dsh-pet/client/GameplayHud
11
11
  */
12
12
  import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
13
- import { touchZoneAt } from "../gameplay.js";
13
+ import { declaredModes, modeStateOf, PET_ROAM_DIRECTIONS, touchZoneAt } from "../gameplay.js";
14
14
  import styles from './pet.module.css';
15
15
  let floatSeq = 0;
16
16
  /** The gameplay overlay for one frames2d pet that declares 'gameplay'. */
@@ -19,7 +19,6 @@ export function GameplayHud(props) {
19
19
  const ui = useSyncExternalStore(store.subscribe, store.getSnapshot);
20
20
  const def = definition.gameplay;
21
21
  const view = ui.snapshot?.gameplay;
22
- const phase = ui.snapshot?.phase ?? 'idle';
23
22
  // Host-persisted skin selection for this pet (undefined = default look).
24
23
  const persistedSkin = ui.snapshot?.skin;
25
24
  const [open, setOpen] = useState(false);
@@ -34,12 +33,19 @@ export function GameplayHud(props) {
34
33
  // Mutable driver state (refs so intervals never re-arm on a poll tick).
35
34
  const modeRef = useRef(view?.mode ?? null);
36
35
  modeRef.current = view?.mode ?? null;
37
- const phaseRef = useRef(phase);
38
- phaseRef.current = phase;
36
+ /** Live gameplay view for the interval loops (def identity is stable, view is not). */
37
+ const viewRef = useRef(view);
38
+ viewRef.current = view;
39
39
  const draggingRef = useRef(false);
40
40
  const touchLockUntilRef = useRef(0);
41
41
  const missRef = useRef(0);
42
42
  const busyRef = useRef(false);
43
+ /** A roam walk owns the visual right now (the idle director must not steal it). */
44
+ const roamHeldRef = useRef(false);
45
+ /** A one-shot idle-director act owns the visual right now (the roam must not cut in). */
46
+ const actHeldRef = useRef(false);
47
+ /** Pending release of the roam's walk track (cleared when another owner takes it). */
48
+ const roamTimerRef = useRef(0);
43
49
  // Dynamic-key lookups (stat ids / currency ids are manifest data).
44
50
  const tr = props.t;
45
51
  const statLabel = (name) => tr('pet.gameplay.stat.' + name);
@@ -55,6 +61,19 @@ export function GameplayHud(props) {
55
61
  if (result.view !== undefined)
56
62
  store.actions.setGameplayView(result.view);
57
63
  };
64
+ /**
65
+ * Give up the roam's claim on the shared track-override slot. Every other
66
+ * owner (a mode, a touch reaction, a drag) calls this before it takes the
67
+ * slot, so a walk that is still in flight can never release someone else's
68
+ * track when its own hold window elapses.
69
+ */
70
+ const yieldRoam = () => {
71
+ if (roamTimerRef.current !== 0) {
72
+ window.clearTimeout(roamTimerRef.current);
73
+ roamTimerRef.current = 0;
74
+ }
75
+ roamHeldRef.current = false;
76
+ };
58
77
  // Tap handling (registered on the bus; PetSprite reports sprite-box
59
78
  // fractions). Sleep wakes on tap; work blocks taps; a held touch
60
79
  // animation turns taps into the plain-click boost.
@@ -69,6 +88,7 @@ export function GameplayHud(props) {
69
88
  // consecutive taps cannot retrigger mid-play (skin click actions and
70
89
  // default touch-zone reactions share this path).
71
90
  const holdTrack = (track, holdMs) => {
91
+ yieldRoam();
72
92
  bus.setTrack?.(track);
73
93
  touchLockUntilRef.current = Date.now() + holdMs;
74
94
  window.setTimeout(() => {
@@ -86,7 +106,9 @@ export function GameplayHud(props) {
86
106
  // base idle, so holding the lock for the full loop is unnecessary).
87
107
  const trackDuration = (track) => definition.frames2d?.tracks[track]?.durations.reduce((sum, ms) => sum + ms, 0) ?? 0;
88
108
  bus.tap = (fx, fy) => {
89
- if (modeRef.current === 'sleep') {
109
+ // Any non-work mode ends on a tap (sleep wakes up, a bath steps out);
110
+ // work keeps swallowing taps while its round is running.
111
+ if (modeRef.current !== null && modeRef.current !== 'work') {
90
112
  void api.setMode(null).then(applyResult, () => undefined);
91
113
  return;
92
114
  }
@@ -180,21 +202,25 @@ export function GameplayHud(props) {
180
202
  return () => { window.removeEventListener('resize', place); };
181
203
  // eslint-disable-next-line react-hooks/exhaustive-deps -- one placement per open/page change
182
204
  }, [open, page]);
183
- // Drag gestures wake a sleeping pet (miku behavior); the drag stream also
184
- // feeds the idle director's suppression check.
205
+ // Drag gestures end any non-work mode (sleep wakes, a bath steps out); the
206
+ // drag stream also feeds the idle director's suppression check.
185
207
  useEffect(() => {
186
208
  return props.drag.subscribe((dragging) => {
187
209
  draggingRef.current = dragging;
188
- if (dragging && modeRef.current === 'sleep') {
210
+ if (dragging)
211
+ yieldRoam();
212
+ if (dragging && modeRef.current !== null && modeRef.current !== 'work') {
189
213
  void api.setMode(null).then(applyResult, () => undefined);
190
214
  }
191
215
  });
192
216
  // eslint-disable-next-line react-hooks/exhaustive-deps -- one subscription per drag stream
193
217
  }, [props.drag]);
194
218
  // Idle director: weighted rolls between staying idle and playing an act.
195
- // Runs only while the phase mapping owns the visual (idle phase, no mode,
196
- // no drag, no held touch animation); maxMiss forces an act after too many
197
- // idle rolls in a row.
219
+ // Rolls in every phase -- an ambient act is not a waiting-room performance,
220
+ // and the idle phase only appears when neither the agent nor the user is busy
221
+ // -- but only while nothing else owns the visual: no mode, no drag, no held
222
+ // touch animation, no roam walk and no act already playing. maxMiss forces an
223
+ // act after too many idle rolls in a row.
198
224
  useEffect(() => {
199
225
  const director = def?.idleDirector;
200
226
  if (def === undefined || director === undefined)
@@ -202,13 +228,16 @@ export function GameplayHud(props) {
202
228
  const total = director.idleWeight + director.acts.reduce((sum, act) => sum + act.weight, 0);
203
229
  if (total <= 0)
204
230
  return undefined;
231
+ let actTimer = 0;
205
232
  const timer = window.setInterval(() => {
206
- if (phaseRef.current !== 'idle')
207
- return;
208
233
  if (modeRef.current !== null || draggingRef.current)
209
234
  return;
210
235
  if (Date.now() < touchLockUntilRef.current)
211
236
  return;
237
+ if (roamHeldRef.current)
238
+ return; // a roam walk owns the visual
239
+ if (actHeldRef.current)
240
+ return; // the previous act is still playing
212
241
  let pickedAct;
213
242
  if (missRef.current >= director.maxMiss) {
214
243
  // Forced act: pick among the acts only.
@@ -237,14 +266,24 @@ export function GameplayHud(props) {
237
266
  return;
238
267
  }
239
268
  missRef.current = 0;
269
+ actHeldRef.current = true;
240
270
  bus.setTrack?.(pickedAct.track);
271
+ // A one-shot act owns the visual until its own duration elapses (the
272
+ // renderer then falls back to the phase map); the roam must not cut in.
273
+ const holdMs = definition.frames2d?.tracks[pickedAct.track]?.durations.reduce((sum, ms) => sum + ms, 0) ?? 0;
274
+ window.clearTimeout(actTimer);
275
+ actTimer = window.setTimeout(() => { actHeldRef.current = false; }, holdMs > 0 ? holdMs : 3000);
241
276
  // Acts with a phrase pool speak one line while they play (miku parity).
242
277
  if (pickedAct.phrases !== undefined && pickedAct.phrases.length > 0) {
243
278
  const phrase = pickedAct.phrases[Math.floor(Math.random() * pickedAct.phrases.length)];
244
279
  store.actions.setFeedback({ text: phrase, kind: 'none', at: Date.now() });
245
280
  }
246
281
  }, director.intervalMs);
247
- return () => window.clearInterval(timer);
282
+ return () => {
283
+ window.clearInterval(timer);
284
+ window.clearTimeout(actTimer);
285
+ actHeldRef.current = false;
286
+ };
248
287
  // eslint-disable-next-line react-hooks/exhaustive-deps -- one director per pet definition
249
288
  }, [definition.id, def]);
250
289
  // Work loop: hold the work track, adjudicate one round per tick, play the
@@ -290,24 +329,99 @@ export function GameplayHud(props) {
290
329
  };
291
330
  // eslint-disable-next-line react-hooks/exhaustive-deps -- the loop keys on the mode and the selected skin
292
331
  }, [definition.id, def, view?.mode, skinId]);
293
- // Sleep loop: hold the sleep track; restore is host-side (lazy settle).
294
- // While a skin with a gameplayTracks.sleep override is selected, the skin's
295
- // own track replaces the default sleep track (e.g. a skin-specific doze).
332
+ // Mode loop: hold the active non-work mode's track (sleep, a bath, any
333
+ // extra 'modes' entry). The stat restore is host-side (lazy settle). While a
334
+ // skin with a matching gameplayTracks override is selected, the skin's own
335
+ // track replaces the default one (e.g. a skin-specific doze).
296
336
  useEffect(() => {
297
- const sleep = def?.sleep;
298
- if (def === undefined || sleep === undefined || view?.mode !== 'sleep')
337
+ const active = view?.mode;
338
+ if (def === undefined || active === undefined || active === null || active === 'work')
339
+ return undefined;
340
+ const hold = modeStateOf(def, active);
341
+ if (hold === undefined)
299
342
  return undefined;
300
343
  const skinGameplay = definition.frames2d?.skins?.find(skin => skin.id === skinIdRef.current)?.gameplayTracks;
301
- const hold = skinGameplay?.['sleep'] ?? sleep.state;
302
- bus.setTrack?.(hold);
344
+ yieldRoam();
345
+ bus.setTrack?.(skinGameplay?.[active] ?? hold);
303
346
  return () => bus.setTrack?.(undefined);
304
347
  // eslint-disable-next-line react-hooks/exhaustive-deps -- the loop keys on the mode value
305
348
  }, [definition.id, def, view?.mode, skinId]);
349
+ // Random roaming (manifest 'roam'): on a slow interval -- in any phase, as long
350
+ // as no mode, drag, touch animation or director act owns the pet -- roll a
351
+ // wander. The chrome walks the sprite and persists the new spot; this side
352
+ // holds the walk track for exactly the travel time.
353
+ useEffect(() => {
354
+ const roam = def?.roam;
355
+ if (def === undefined || roam === undefined)
356
+ return undefined;
357
+ const roll = () => {
358
+ // Every phase: a wandering pet is ambient, not a performance, so she
359
+ // keeps walking while the agent works (the idle director's acts stay
360
+ // idle-only). A mode, a drag, a touch animation and a director
361
+ // act all still own the pet and suppress the roll.
362
+ if (modeRef.current !== null || draggingRef.current)
363
+ return;
364
+ if (Date.now() < touchLockUntilRef.current)
365
+ return;
366
+ if (roamHeldRef.current || actHeldRef.current)
367
+ return;
368
+ if (Math.random() >= roam.probability)
369
+ return;
370
+ const span = roam.distanceMax - roam.distanceMin;
371
+ const distance = roam.distanceMin + Math.random() * span;
372
+ const directions = roam.directions ?? PET_ROAM_DIRECTIONS;
373
+ const direction = directions[Math.floor(Math.random() * directions.length)] ?? 'left';
374
+ const travelled = bus.walk?.(direction, distance, roam.speed) ?? 0;
375
+ if (travelled === 0)
376
+ return;
377
+ roamHeldRef.current = true;
378
+ bus.setTrack?.(roam.state);
379
+ roamTimerRef.current = window.setTimeout(() => {
380
+ roamTimerRef.current = 0;
381
+ if (!roamHeldRef.current)
382
+ return; // another owner already took the slot
383
+ roamHeldRef.current = false;
384
+ bus.setTrack?.(undefined);
385
+ }, Math.max(150, (Math.abs(travelled) / roam.speed) * 1000));
386
+ };
387
+ // The two decision timers must never share a tick. The roam starts half an
388
+ // interval late, so its ticks interleave with the idle director's instead
389
+ // of landing on the same instant (10 s and 15 s coincide every 30 s).
390
+ let timer = 0;
391
+ const lead = window.setTimeout(() => {
392
+ roll();
393
+ timer = window.setInterval(roll, roam.intervalMs);
394
+ }, Math.round(roam.intervalMs / 2));
395
+ return () => {
396
+ window.clearTimeout(lead);
397
+ window.clearInterval(timer);
398
+ yieldRoam();
399
+ };
400
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- one director per pet definition
401
+ }, [definition.id, def]);
306
402
  if (def === undefined || view === undefined)
307
403
  return null;
308
404
  const mode = view.mode;
309
405
  const stats = def.stats ?? {};
310
406
  const shop = def.shop;
407
+ // Every menu mode beyond work: sleep plus any extra 'modes' the pet declares.
408
+ const menuModes = declaredModes(def);
409
+ /** Action-button label for one mode (active = the button that leaves it). */
410
+ const modeLabel = (name, active) => {
411
+ if (name === 'sleep')
412
+ return tr(active ? 'pet.gameplay.wake' : 'pet.gameplay.sleep');
413
+ const declared = def.modes?.[name];
414
+ return (active ? declared?.activeLabel ?? declared?.label : declared?.label) ?? tr('pet.gameplay.' + name);
415
+ };
416
+ /** Chip label for the mode the pet is in right now. */
417
+ const modeChip = (name) => {
418
+ if (name === 'work')
419
+ return tr('pet.gameplay.working');
420
+ if (name === 'sleep')
421
+ return tr('pet.gameplay.sleeping');
422
+ const declared = def.modes?.[name];
423
+ return declared?.activeLabel ?? declared?.label ?? tr('pet.gameplay.' + name);
424
+ };
311
425
  const buy = (itemId) => {
312
426
  void api.buy(itemId).then((result) => {
313
427
  applyResult(result);
@@ -362,8 +476,8 @@ export function GameplayHud(props) {
362
476
  restore();
363
477
  }, restore);
364
478
  };
365
- return (_jsxs("div", { ref: hudRef, className: styles.gameplayHud, "data-dsh-pet-gameplay": definition.id, children: [floats.map(entry => (_jsx("div", { className: styles.gameplayFloat, children: entry.text }, entry.id))), mode !== null && (_jsx("div", { className: styles.gameplayModeChip, children: tr(mode === 'work' ? 'pet.gameplay.working' : 'pet.gameplay.sleeping') })), open && (_jsxs("div", { ref: cardRef, className: styles.gameplayCard, "data-page": page, children: [page === 'root' && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.gameplayBars, children: Object.entries(stats).map(([name, stat]) => {
479
+ return (_jsxs("div", { ref: hudRef, className: styles.gameplayHud, "data-dsh-pet-gameplay": definition.id, children: [floats.map(entry => (_jsx("div", { className: styles.gameplayFloat, children: entry.text }, entry.id))), mode !== null && (_jsx("div", { className: styles.gameplayModeChip, children: modeChip(mode) })), open && (_jsxs("div", { ref: cardRef, className: styles.gameplayCard, "data-page": page, children: [page === 'root' && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.gameplayBars, children: Object.entries(stats).map(([name, stat]) => {
366
480
  const value = view.stats[name] ?? 0;
367
481
  return (_jsxs("div", { className: styles.gameplayBarRow, title: statLabel(name) + ' ' + String(value) + '/' + String(stat.max), children: [_jsx("span", { className: styles.gameplayBarLabel, children: statLabel(name) }), _jsx("span", { className: styles.gameplayBarTrack, children: _jsx("span", { className: styles.gameplayBarFill, style: { width: Math.round((value / stat.max) * 100) + '%' } }) })] }, name));
368
- }) }), _jsxs("div", { className: styles.gameplayActions, children: [def.sleep !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setMode(mode === 'sleep' ? null : 'sleep'), children: tr(mode === 'sleep' ? 'pet.gameplay.wake' : 'pet.gameplay.sleep') })), shop !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setPage('shop'), children: tr('pet.gameplay.shop') })), skins !== undefined && skins.length > 0 && (_jsx("button", { type: "button", className: styles.action, onClick: () => setPage('skins'), children: tr('pet.gameplay.skin') })), def.work !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setMode(mode === 'work' ? null : 'work'), children: tr(mode === 'work' ? 'pet.gameplay.stopWork' : 'pet.gameplay.work') }))] })] })), page === 'shop' && shop !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.gameplayShopItems, children: shop.items.map(item => (_jsxs("button", { type: "button", className: styles.gameplayShopItem, onClick: () => buy(item.id), title: item.label + ' — ' + String(item.price) + ' ' + currencyLabel(item.currency), children: [item.image !== undefined && (_jsx("img", { className: styles.gameplayShopItemImage, src: item.image, alt: "", draggable: false })), _jsx("span", { className: styles.gameplayShopItemLabel, children: item.label }), _jsxs("span", { className: styles.gameplayShopItemPrice, children: [item.price, " ", currencyLabel(item.currency)] })] }, item.id))) }), _jsx("div", { className: styles.gameplayActions, children: _jsx("button", { type: "button", className: styles.action, onClick: () => setPage('root'), children: tr('pet.gameplay.back') }) })] })), page === 'skins' && skins !== undefined && (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.gameplaySkinItems, children: [_jsx("button", { type: "button", className: skinId === undefined ? styles.gameplaySkinItem + ' ' + styles.gameplaySkinItemActive : styles.gameplaySkinItem, onClick: () => selectSkin(undefined), children: tr('pet.gameplay.skinDefault') }), skins.map(skin => (_jsx("button", { type: "button", className: skinId === skin.id ? styles.gameplaySkinItem + ' ' + styles.gameplaySkinItemActive : styles.gameplaySkinItem, onClick: () => selectSkin(skin), children: skin.label }, skin.id)))] }), _jsx("div", { className: styles.gameplayActions, children: _jsx("button", { type: "button", className: styles.action, onClick: () => setPage('root'), children: tr('pet.gameplay.back') }) })] })), _jsx("button", { type: "button", className: styles.gameplayClose, "aria-label": tr('pet.gameplay.back'), onClick: () => setOpen(false), children: "\u00D7" })] }))] }));
482
+ }) }), _jsxs("div", { className: styles.gameplayActions, children: [menuModes.map(name => (_jsx("button", { type: "button", className: styles.action, onClick: () => setMode(mode === name ? null : name), children: modeLabel(name, mode === name) }, name))), shop !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setPage('shop'), children: tr('pet.gameplay.shop') })), skins !== undefined && skins.length > 0 && (_jsx("button", { type: "button", className: styles.action, onClick: () => setPage('skins'), children: tr('pet.gameplay.skin') })), def.work !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setMode(mode === 'work' ? null : 'work'), children: tr(mode === 'work' ? 'pet.gameplay.stopWork' : 'pet.gameplay.work') }))] })] })), page === 'shop' && shop !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.gameplayShopItems, children: shop.items.map(item => (_jsxs("button", { type: "button", className: styles.gameplayShopItem, onClick: () => buy(item.id), title: item.label + ' — ' + String(item.price) + ' ' + currencyLabel(item.currency), children: [item.image !== undefined && (_jsx("img", { className: styles.gameplayShopItemImage, src: item.image, alt: "", draggable: false })), _jsx("span", { className: styles.gameplayShopItemLabel, children: item.label }), _jsxs("span", { className: styles.gameplayShopItemPrice, children: [item.price, " ", currencyLabel(item.currency)] })] }, item.id))) }), _jsx("div", { className: styles.gameplayActions, children: _jsx("button", { type: "button", className: styles.action, onClick: () => setPage('root'), children: tr('pet.gameplay.back') }) })] })), page === 'skins' && skins !== undefined && (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.gameplaySkinItems, children: [_jsx("button", { type: "button", className: skinId === undefined ? styles.gameplaySkinItem + ' ' + styles.gameplaySkinItemActive : styles.gameplaySkinItem, onClick: () => selectSkin(undefined), children: tr('pet.gameplay.skinDefault') }), skins.map(skin => (_jsx("button", { type: "button", className: skinId === skin.id ? styles.gameplaySkinItem + ' ' + styles.gameplaySkinItemActive : styles.gameplaySkinItem, onClick: () => selectSkin(skin), children: skin.label }, skin.id)))] }), _jsx("div", { className: styles.gameplayActions, children: _jsx("button", { type: "button", className: styles.action, onClick: () => setPage('root'), children: tr('pet.gameplay.back') }) })] })), _jsx("button", { type: "button", className: styles.gameplayClose, "aria-label": tr('pet.gameplay.back'), onClick: () => setOpen(false), children: "\u00D7" })] }))] }));
369
483
  }
@@ -13,7 +13,7 @@
13
13
  * @module @linxin666/dsh-pet/client
14
14
  */
15
15
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
16
- import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
16
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
17
17
  /** Required services (sessions powers bubble-to-session navigation). */
18
18
  export declare const inject: string[];
19
19
  /** Re-exported for consumers that type against the injected face. */
@@ -26,12 +26,15 @@ export type { PetDefinition } from '../registry.ts';
26
26
  declare module '@deepseek-ai/cordis' {
27
27
  interface Context {
28
28
  /**
29
- * Optional rc.6 compatibility binder provided by dsh-web-settings;
30
- * absent when that group plugin is not installed, so callers fall back to
31
- * the official settings scope.
29
+ * Optional family binder provided by dsh-web-settings; absent when that
30
+ * group plugin is not installed, so callers fall back to the shared
31
+ * per-entry form.
32
32
  */
33
33
  webUiSettings?: {
34
- bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S>;
34
+ bind<S>(spec: {
35
+ namespace: string;
36
+ decode?: (section: unknown) => S | undefined;
37
+ }): ConfigForm<S>;
35
38
  };
36
39
  }
37
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAiFlG,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;AAuBD,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAqU9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,4CAA4C,CAAA;AAqFzF,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAAwF,CAAA;AAE3G,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;YACd,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,CAAC,GAAG,SAAS,CAAA;aAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;SAClG,CAAA;KACF;CACF;AA0DD,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoU9C"}
@@ -23,6 +23,7 @@ import { frames2dRenderer } from "./renderers/frames2d.js";
23
23
  import { registerPetUiTeardown, takeoverPetUiTeardown } from "./ui-teardown.js";
24
24
  import { PetSettingsSection, PetSettingsCardController } from "./PetSettingsCard.js";
25
25
  import { NS, en, zh, t } from "./locales.js";
26
+ import { mainViewSessionId } from "./main-session.js";
26
27
  import { reportDailyHeartbeat } from "./telemetry.js";
27
28
  /** Same-origin JSON fetch helper (GET without body, POST with JSON body). */
28
29
  async function petFetch(path, body) {
@@ -56,10 +57,44 @@ const petApi = {
56
57
  };
57
58
  /** Poll interval for the host snapshot. */
58
59
  const POLL_MS = 2000;
59
- /** Settings namespace the pet settings card edits (the Host plugin registers it). */
60
+ /** Settings namespace of the pet's settings page. It is the profile entry id the Host serves a form for. */
60
61
  const PET_SETTINGS_NS = 'pet';
61
62
  /** Required services (sessions powers bubble-to-session navigation). */
62
- export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote', 'sessions'];
63
+ export const inject = ['slots', 'locale', 'connection', 'configForms', 'remote', 'sessions', 'uiWorkspace'];
64
+ /**
65
+ * The configuration form the settings card stages and saves through: the
66
+ * family binder when dsh-web-settings is mounted, otherwise the shared form of
67
+ * this plugin's own profile entry.
68
+ *
69
+ * The two are not interchangeable by name. A plugin's settings ARE its own
70
+ * config on 0.1.7, so the form belongs to the profile entry that carries the
71
+ * plugin — and only the family binder knows which entry that is, because the
72
+ * family bundle renames child rows ('pet' becomes 'web-ui-pet' there) while a
73
+ * standalone install keeps the package's own id.
74
+ * @param ctx - client root context.
75
+ * @returns the form for the pet's settings page.
76
+ */
77
+ const AGGREGATE_ENTRY_ID = 'web-ui-pet';
78
+ const PET_ENTRY_IDS = [AGGREGATE_ENTRY_ID, 'ui-pet', PET_SETTINGS_NS];
79
+ function servedEntryId(forms) {
80
+ let served;
81
+ try {
82
+ served = forms.describe().getSnapshot().view?.namespaces.map(view => view.ns);
83
+ }
84
+ catch {
85
+ served = undefined;
86
+ }
87
+ if (!served || served.length === 0)
88
+ return PET_SETTINGS_NS;
89
+ return PET_ENTRY_IDS.find(id => served.includes(id)) ?? PET_SETTINGS_NS;
90
+ }
91
+ function petSettingsForm(ctx) {
92
+ const binder = ctx.get('webUiSettings');
93
+ if (binder !== undefined && typeof binder.bind === 'function') {
94
+ return binder.bind({ namespace: PET_SETTINGS_NS });
95
+ }
96
+ return ctx.configForms.get(servedEntryId(ctx.configForms));
97
+ }
63
98
  /**
64
99
  * Client plugin body: register dictionaries, mount the global pet entry and
65
100
  * poll loop while the plugin is enabled, and seat the settings card as a
@@ -94,23 +129,22 @@ export function apply(ctx) {
94
129
  // M3). Registration is idempotent (id wins), so re-applies stay clean.
95
130
  defaultPetRendererRegistry.register(live2dRenderer);
96
131
  defaultPetRendererRegistry.register(frames2dRenderer);
97
- const binder = ctx.get('webUiSettings') ?? ctx.settingsScope;
98
- const settingsScope = binder.bind({ namespace: PET_SETTINGS_NS });
132
+ const settingsForm = petSettingsForm(ctx);
99
133
  const enabled = () => {
100
- const snapshot = settingsScope.getSnapshot();
134
+ const snapshot = settingsForm.getSnapshot();
101
135
  return snapshot.status === 'ready'
102
136
  ? snapshot.value?.enabled ?? true
103
137
  : snapshot.status === 'unavailable';
104
138
  };
105
- // First-level settings section: one staged form over the 'pet' settings
106
- // namespace, registered as a top-level settings page. The controller loads
107
- // the petId choices from the registry endpoint itself — the registry lists
108
- // the available pets (built-in assets plus user dirs), so the section only
109
- // ever shows installed pets. Installing new pets happens in the Workshop
110
- // store.
111
- const petSettings = new PetSettingsCardController(settingsScope);
139
+ // First-level settings section: one staged form over the 'pet' profile
140
+ // entry's configuration, registered as a top-level settings page. The
141
+ // controller loads the petId choices from the registry endpoint itself — the
142
+ // registry lists the available pets (built-in assets plus user dirs), so the
143
+ // section only ever shows installed pets. Installing new pets happens in the
144
+ // Workshop store.
145
+ const petSettings = new PetSettingsCardController(settingsForm);
112
146
  // The section entry owns the controller: unregistering it (fiber disposal,
113
- // hot reload) releases the scope subscription through petSettings.dispose.
147
+ // hot reload) releases the form subscription through petSettings.dispose.
114
148
  ctx.slots.inject('settings.section', () => {
115
149
  try {
116
150
  const unregister = ctx.slots.register({
@@ -159,19 +193,17 @@ export function apply(ctx) {
159
193
  const setPets = petStore.actions.setPets;
160
194
  const setState = petStore.actions.setState;
161
195
  const setFeedback = petStore.actions.setFeedback;
162
- // Clicking a session bubble jumps the GUI to that session; the same
163
- // sessions face reports which session the user is currently on, so the
164
- // host can lead the bubble stack with it. A bubble can outlive its
165
- // disposed session by one poll tick, and the sessions service fails
166
- // loud on unknown ids, so consult the live list first. The pet's type
167
- // program also loads the host-side dsh-session package through the
168
- // service types, whose Context merge declares a different 'sessions'
169
- // face; pin the browser runtime's outward face here.
196
+ // Clicking a session bubble jumps the GUI to that session; the
197
+ // catalog's main-view ownership marker tells which session the main view
198
+ // holds (the multi-instance replacement for the removed list.current), so
199
+ // the host can lead the bubble stack with it. A bubble can outlive its
200
+ // disposed session by one poll tick, and navigation rejects unknown ids,
201
+ // so consult the live list first. The pet's type program also loads the
202
+ // host-side dsh-session package through the service types, whose Context
203
+ // merge declares a different 'sessions' face; pin the browser runtime's
204
+ // outward face here.
170
205
  const sessions = ctx.sessions;
171
- const currentSessionId = () => {
172
- const current = sessions.list.getSnapshot().current;
173
- return current === undefined ? undefined : String(current);
174
- };
206
+ const currentSessionId = () => mainViewSessionId(sessions.list.getSnapshot().byId);
175
207
  // The registry list is fetched lazily with retries baked into the poll
176
208
  // cycle: until it lands, the dock entry renders nothing and every 2s
177
209
  // tick tries again. After it lands, one list feeds both the sprite and
@@ -251,7 +283,9 @@ export function apply(ctx) {
251
283
  const list = sessions.list.getSnapshot();
252
284
  if (list.byId[sessionId] === undefined)
253
285
  return;
254
- sessions.open(sessionId);
286
+ // Navigation belongs to the workspace UI since the multi-instance Client
287
+ // Session model; the Session Controller no longer opens one.
288
+ ctx.uiWorkspace.openSession(sessionId);
255
289
  };
256
290
  const injected = () => ({
257
291
  store: petStore,
@@ -393,7 +427,7 @@ export function apply(ctx) {
393
427
  // (issue #785): disposal drops the subscription and tears the UI down
394
428
  // (terminal), so a hot-reloaded or re-injected bundle never leaves the
395
429
  // previous React root, container, or poll loop behind on document.body.
396
- const unsubscribeSettings = settingsScope.subscribe(syncUi);
430
+ const unsubscribeSettings = settingsForm.subscribe(syncUi);
397
431
  ctx.effect(() => () => {
398
432
  unsubscribeSettings();
399
433
  killUi();
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Main-view Session derivation for the multi-instance Client Session model.
3
+ *
4
+ * The Client Session Controller carries no global "current" selection: view
5
+ * selection belongs to the workspace UI, which owns the main-area reference and
6
+ * publishes it through the catalog's per-source ownership counts
7
+ * (`SessionSummary.retainedBy.mainView`). A plugin that needs the Session the
8
+ * main view shows derives it from that marker instead of the removed
9
+ * `SessionListState.current` field.
10
+ *
11
+ * The marker describes view ownership, not authority. It is the right input for
12
+ * presentation and for deciding which view a navigation targets; it is not a
13
+ * business fallback, because source counts never imply visibility or success.
14
+ * @module dsh-web/shared/main-session
15
+ */
16
+ /** The catalog row subset this helper reads (a `SessionSummary` projection). */
17
+ export interface MainViewOwnedRow<Id extends string = string> {
18
+ /** Catalog row identity. */
19
+ readonly id: Id;
20
+ /** Per-source local ownership counts; absent rows are never owned. */
21
+ readonly retainedBy?: Readonly<Partial<Record<string, number>>> | undefined;
22
+ }
23
+ /**
24
+ * Resolve the Session the main view currently shows.
25
+ *
26
+ * Reads the catalog's rows rather than a per-id retain-info source: ownership
27
+ * counts ride the list snapshot, so this neither allocates observers nor opens
28
+ * history, and a subscription to the list still fires when the selection moves.
29
+ * @param byId - the session catalog's rows (`SessionListState.byId`), when available.
30
+ * @returns the main-view session id, or undefined when the main view shows none.
31
+ */
32
+ export declare function mainViewSessionId<Id extends string>(byId: Readonly<Record<Id, MainViewOwnedRow<Id> | undefined>> | undefined | null): Id | undefined;
33
+ //# sourceMappingURL=main-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-session.d.ts","sourceRoot":"","sources":["../../../src/client/main-session.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAEH,gFAAgF;AAChF,MAAM,WAAW,gBAAgB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM;IAC1D,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAA;IACf,sEAAsE;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;CAC5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,MAAM,EACjD,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,GAC9E,EAAE,GAAG,SAAS,CAMhB"}
@@ -0,0 +1,34 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/client/main-session.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * Main-view Session derivation for the multi-instance Client Session model.
4
+ *
5
+ * The Client Session Controller carries no global "current" selection: view
6
+ * selection belongs to the workspace UI, which owns the main-area reference and
7
+ * publishes it through the catalog's per-source ownership counts
8
+ * (`SessionSummary.retainedBy.mainView`). A plugin that needs the Session the
9
+ * main view shows derives it from that marker instead of the removed
10
+ * `SessionListState.current` field.
11
+ *
12
+ * The marker describes view ownership, not authority. It is the right input for
13
+ * presentation and for deciding which view a navigation targets; it is not a
14
+ * business fallback, because source counts never imply visibility or success.
15
+ * @module dsh-web/shared/main-session
16
+ */
17
+ /**
18
+ * Resolve the Session the main view currently shows.
19
+ *
20
+ * Reads the catalog's rows rather than a per-id retain-info source: ownership
21
+ * counts ride the list snapshot, so this neither allocates observers nor opens
22
+ * history, and a subscription to the list still fires when the selection moves.
23
+ * @param byId - the session catalog's rows (`SessionListState.byId`), when available.
24
+ * @returns the main-view session id, or undefined when the main view shows none.
25
+ */
26
+ export function mainViewSessionId(byId) {
27
+ if (byId === undefined || byId === null)
28
+ return undefined;
29
+ for (const row of Object.values(byId)) {
30
+ if (row !== undefined && (row.retainedBy?.mainView ?? 0) > 0)
31
+ return row.id;
32
+ }
33
+ return undefined;
34
+ }
@@ -6,7 +6,7 @@
6
6
  * each plugin's client bundle; mirrors the official ui-plugin-config
7
7
  * card-store pattern.
8
8
  */
9
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
9
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
10
10
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
11
11
  /** The write one field's staged text performs when the card is saved. */
12
12
  export type FieldWrite = {
@@ -86,7 +86,7 @@ export interface CardActions {
86
86
  /** Drop every staged edit. */
87
87
  discard: () => void;
88
88
  }
89
- /** One durable write inside the save's atomic scope mutation. */
89
+ /** One durable write inside the save's atomic form mutation. */
90
90
  export interface BatchedWrite {
91
91
  /** Field this entry writes. */
92
92
  field: string;
@@ -130,20 +130,20 @@ export declare class CardForm<T> {
130
130
  private readonly specs;
131
131
  private readonly staged;
132
132
  private readonly listeners;
133
- /** The scope subscription installed in the constructor; released by dispose(). */
134
- private readonly disposeScope;
133
+ /** The form subscription installed in the constructor; released by dispose(). */
134
+ private readonly disposeForm;
135
135
  private disposed;
136
136
  private saving;
137
137
  private failed;
138
138
  private failedReason;
139
- /** @param scope - the bound settings scope for this card's namespace. */
140
- constructor(scope: SettingsScope<T>, specs: FieldSpec[]);
139
+ /** @param scope - the bound configuration form for this card's namespace. */
140
+ constructor(scope: ConfigForm<T>, specs: FieldSpec[]);
141
141
  /**
142
- * Release the scope subscription and every bound store listener. The card
142
+ * Release the form subscription and every bound store listener. The card
143
143
  * must call this on teardown; later calls are no-ops.
144
144
  */
145
145
  dispose(): void;
146
- /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
146
+ /** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
147
147
  bind<S>(project: () => S): SnapshotStore<S>;
148
148
  /** Read the card-level state: what the Host serves, and what a save would do. */
149
149
  shell(): CardShell;
@@ -152,19 +152,19 @@ export declare class CardForm<T> {
152
152
  /** The actions the card's slot registration injects. */
153
153
  actions(): CardActions;
154
154
  /**
155
- * Write every staged edit in one atomic scope mutation, then re-seed from
155
+ * Write every staged edit in one atomic form mutation, then re-seed from
156
156
  * what the Host accepted.
157
157
  *
158
158
  * The whole batch rides one mutate, so cross-field validate hooks
159
159
  * (baseURL+model) judge it as a unit: the Host either applies every write
160
- * or refuses the batch. The 0.1.2 scope contract never rejects a refused
161
- * mutation — the scope recovers with a fresh Host view and resolves — so
162
- * resolution alone proves nothing: the outcome is judged by reading the
163
- * settled snapshot back, one planned write at a time, and one missed write
164
- * fails the whole save. A scope that still rejects on refusal (the dsh-web
165
- * bridge scope) reports through the same failure path with its rejection
166
- * message. A save that did not land keeps its drafts, so the user can
167
- * correct them instead of retyping.
160
+ * or refuses the batch. The form contract answers a refusal or a skipped
161
+ * write with `false` (it recovers with a fresh Host view instead of
162
+ * throwing), so the outcome is judged twice: the answer itself, and then the
163
+ * settled snapshot read back one planned write at a time. One missed write
164
+ * fails the whole save. A transport that rejects instead (the dsh-web bridge
165
+ * controller on a dead connection) reports through the same failure path
166
+ * with its rejection message. A save that did not land keeps its drafts, so
167
+ * the user can correct them instead of retyping.
168
168
  * @returns settlement after the mutation and the read-back.
169
169
  */
170
170
  save(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,4CAA4C,CAAA;AACtG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGlE,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2FAA2F;IAC3F,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC;;;OAGG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;CAChD;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,KAAK,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAA;IAChB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAA;IACf,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,gFAAgF;IAChF,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,yEAAyE;IACzE,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAwBD,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,EAAE,EAAE,KAAK,GAAG,OAAO,CAAA;IACnB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,6JAA6J;AAC7J,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,iBAAsB,GAAG,SAAS,CAezF;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CASlD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAEpD;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAYrD;AAED,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAShF;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ,CAAC,CAAC;IAanB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAZxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAY;IACzC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAoB;IAExC,yEAAyE;gBAEtD,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACxC,KAAK,EAAE,SAAS,EAAE;IAMpB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAOf,wFAAwF;IACxF,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;IAM3C,iFAAiF;IACjF,KAAK,IAAI,SAAS;IAelB,8EAA8E;IAC9E,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAchC,wDAAwD;IACxD,OAAO,IAAI,WAAW;IAiBtB;;;;;;;;;;;;;;;OAeG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC3B;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;IAiBZ;;;OAGG;IACH,OAAO,CAAC,SAAS;IAUjB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,OAAO;CAGhB"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}