@linxin666/dsh-pet 0.1.14 → 0.1.16

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 (90) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +73 -38
  3. package/README.zh.md +91 -55
  4. package/assets/whale/pet.json +12 -1
  5. package/cordis.patch.yml +4 -4
  6. package/lib/client.js +296 -347
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +975 -317
  9. package/lib/invariant.js +1 -1
  10. package/lib/{state-P-wsVJ6O.js → state-CFyJv0sQ.js} +15 -1
  11. package/lib/types/affinity.d.ts +15 -0
  12. package/lib/types/affinity.d.ts.map +1 -1
  13. package/lib/types/affinity.js +14 -0
  14. package/lib/types/client/PetDockEntry.d.ts +14 -12
  15. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  16. package/lib/types/client/PetDockEntry.js +13 -8
  17. package/lib/types/client/PetSettingsCard.d.ts +22 -9
  18. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  19. package/lib/types/client/PetSettingsCard.js +42 -7
  20. package/lib/types/client/{WhalePet.d.ts → PetSprite.d.ts} +21 -20
  21. package/lib/types/client/PetSprite.d.ts.map +1 -0
  22. package/lib/types/client/{WhalePet.js → PetSprite.js} +44 -67
  23. package/lib/types/client/PluginSettingsCard.d.ts +26 -11
  24. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  25. package/lib/types/client/PluginSettingsCard.js +30 -8
  26. package/lib/types/client/index.d.ts +11 -8
  27. package/lib/types/client/index.d.ts.map +1 -1
  28. package/lib/types/client/index.js +32 -14
  29. package/lib/types/client/locales.d.ts +10 -10
  30. package/lib/types/client/locales.js +10 -10
  31. package/lib/types/client/pet-store.d.ts +5 -0
  32. package/lib/types/client/pet-store.d.ts.map +1 -1
  33. package/lib/types/client/pet-store.js +4 -0
  34. package/lib/types/client/settings-form.d.ts +76 -5
  35. package/lib/types/client/settings-form.d.ts.map +1 -1
  36. package/lib/types/client/settings-form.js +97 -20
  37. package/lib/types/client/spritesheet.d.ts +14 -48
  38. package/lib/types/client/spritesheet.d.ts.map +1 -1
  39. package/lib/types/client/spritesheet.js +13 -81
  40. package/lib/types/dsh-home.d.ts +17 -0
  41. package/lib/types/dsh-home.d.ts.map +1 -0
  42. package/lib/types/dsh-home.js +34 -0
  43. package/lib/types/event-projection.d.ts +36 -0
  44. package/lib/types/event-projection.d.ts.map +1 -0
  45. package/lib/types/event-projection.js +98 -0
  46. package/lib/types/index.d.ts +24 -11
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/index.js +53 -33
  49. package/lib/types/ledger.d.ts +79 -0
  50. package/lib/types/ledger.d.ts.map +1 -0
  51. package/lib/types/ledger.js +146 -0
  52. package/lib/types/persist.d.ts +15 -4
  53. package/lib/types/persist.d.ts.map +1 -1
  54. package/lib/types/persist.js +39 -8
  55. package/lib/types/registry.d.ts +158 -0
  56. package/lib/types/registry.d.ts.map +1 -0
  57. package/lib/types/registry.js +276 -0
  58. package/lib/types/routes.d.ts +10 -10
  59. package/lib/types/routes.d.ts.map +1 -1
  60. package/lib/types/routes.js +169 -51
  61. package/lib/types/service.d.ts +59 -57
  62. package/lib/types/service.d.ts.map +1 -1
  63. package/lib/types/service.js +111 -211
  64. package/package.json +2 -2
  65. package/src/affinity.ts +33 -0
  66. package/src/client/PetDockEntry.tsx +34 -26
  67. package/src/client/PetSettingsCard.tsx +67 -22
  68. package/src/client/{WhalePet.test.tsx → PetSprite.test.tsx} +56 -8
  69. package/src/client/{WhalePet.tsx → PetSprite.tsx} +55 -73
  70. package/src/client/PluginSettingsCard.tsx +90 -18
  71. package/src/client/index.ts +38 -16
  72. package/src/client/locales.ts +10 -10
  73. package/src/client/pet-store.ts +9 -0
  74. package/src/client/settings-card.module.css +9 -0
  75. package/src/client/settings-form.ts +153 -19
  76. package/src/client/spritesheet.ts +16 -92
  77. package/src/dsh-home.test.ts +35 -0
  78. package/src/dsh-home.ts +36 -0
  79. package/src/event-projection.ts +128 -0
  80. package/src/index.test.ts +23 -0
  81. package/src/index.ts +89 -45
  82. package/src/ledger.test.ts +69 -0
  83. package/src/ledger.ts +190 -0
  84. package/src/persist.test.ts +52 -3
  85. package/src/persist.ts +49 -11
  86. package/src/registry.test.ts +132 -0
  87. package/src/registry.ts +400 -0
  88. package/src/routes.ts +166 -53
  89. package/src/service.ts +153 -285
  90. package/lib/types/client/WhalePet.d.ts.map +0 -1
@@ -1,36 +1,34 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
- * Whale-girl companion component — the browser half's centerpiece. Renders a
3
+ * Pet sprite companion component — the browser half's centerpiece. Renders a
4
4
  * fixed-position floating sprite (React portal onto document.body), plays
5
- * the spritesheet track matching the host animation snapshot, and exposes
6
- * the interaction surface: click to pet, hover panel with feed/hide, drag to
7
- * reposition (persisted via setConfig).
8
- * @module @linxin666/dsh-pet/client/WhalePet
5
+ * the track matching the host animation snapshot, and exposes the
6
+ * interaction surface: click to pet, hover panel with feed/rename/hide, drag
7
+ * to reposition (persisted via setConfig). Everything visual comes from the
8
+ * pet definition the host serves ('/api/pet/pets' + the state snapshot's
9
+ * pet id), so one component renders every registry entry.
10
+ * @module @linxin666/dsh-pet/client/PetSprite
9
11
  */
10
12
  import { useEffect, useRef, useState } from 'react';
11
13
  import { createPortal } from 'react-dom';
12
- import { framePosition, FRAME_WIDTH, FRAME_HEIGHT, FRAME_COLUMNS, TRACKS, rowOfTrack, trimTrack, detectFrameCounts } from "./spritesheet.js";
14
+ import clsx from 'clsx';
15
+ import { framePosition, rowOfTrack, trimTrack } from "./spritesheet.js";
13
16
  import styles from './pet.module.css';
14
- /** Browser URL of the whale-girl atlas (served by the host half's own route). */
15
- export const PET_SPRITESHEET_URL = '/pet/whale/spritesheet.webp';
16
- /** Browser URL of the whale-girl manifest (authoritative per-row frame counts). */
17
- export const PET_MANIFEST_URL = '/pet/whale/pet.json';
18
17
  /** Clamp a drag offset inside the viewport with a margin. */
19
18
  function clampOffset(value, max) {
20
19
  return Math.max(0, Math.min(max, value));
21
20
  }
22
21
  /**
23
22
  * The floating pet. The spritesheet frame advances on requestAnimationFrame
24
- * with per-frame durations from TRACKS; the atlas image is loaded once and
25
- * the background position is written straight to the sprite element (no
26
- * per-frame React state).
23
+ * with per-frame durations from the definition's tracks; the atlas image is
24
+ * loaded once and the background position is written straight to the sprite
25
+ * element (no per-frame React state).
27
26
  */
28
- export function WhalePet(props) {
29
- const { snapshot, display, feedback } = props;
27
+ export function PetSprite(props) {
28
+ const { snapshot, definition, display, feedback } = props;
30
29
  const spriteRef = useRef(null);
31
30
  const floatRef = useRef(null);
32
31
  const [imageReady, setImageReady] = useState(false);
33
- const [frameCounts, setFrameCounts] = useState(null);
34
32
  const [hovered, setHovered] = useState(false);
35
33
  const [renaming, setRenaming] = useState(false);
36
34
  const [nameDraft, setNameDraft] = useState('');
@@ -42,66 +40,46 @@ export function WhalePet(props) {
42
40
  index: 0,
43
41
  elapsed: 0,
44
42
  });
45
- // Load the atlas once; then resolve per-row frame counts so tracks never
46
- // play the transparent trailing cells of a short row. One decoded Image
47
- // feeds both the sprite render and the frame-count detection. The counts
48
- // prefer the authoritatively recorded `frames` field on the pet.json
49
- // manifest route and only fall back to the getImageData atlas scan when
50
- // that field is absent (older manifests).
43
+ const cell = definition.cell;
44
+ const columns = definition.columns;
45
+ const rows = definition.rows;
46
+ const tracks = definition.tracks;
47
+ // Load the atlas once; the definition carries the authoritative per-row
48
+ // frame counts and per-track durations, so nothing else is fetched.
51
49
  useEffect(() => {
52
50
  let cancelled = false;
53
51
  const img = new Image();
54
52
  img.onload = () => {
55
- if (cancelled)
56
- return;
57
- setImageReady(true);
58
- fetch(PET_MANIFEST_URL)
59
- .then((res) => (res.ok ? res.json() : Promise.resolve({})))
60
- .then((manifest) => {
61
- if (cancelled)
62
- return;
63
- const frames = manifest.frames;
64
- if (Array.isArray(frames) && frames.length === 9 && frames.every((n) => typeof n === 'number')) {
65
- setFrameCounts(frames);
66
- }
67
- else {
68
- setFrameCounts(detectFrameCounts(img));
69
- }
70
- })
71
- .catch(() => {
72
- if (!cancelled)
73
- setFrameCounts(detectFrameCounts(img));
74
- });
53
+ if (!cancelled)
54
+ setImageReady(true);
75
55
  };
76
- img.src = PET_SPRITESHEET_URL;
56
+ img.src = definition.atlasUrl;
77
57
  return () => {
78
58
  cancelled = true;
79
59
  img.onload = null;
80
60
  };
81
- }, []);
61
+ }, [definition.atlasUrl]);
82
62
  // Frame loop: advance the current track and write background-position.
83
63
  // Offsets must be in SCALED coordinates (background-position applies to the
84
64
  // scaled background image), so the current sprite scale rides a ref that
85
65
  // the loop reads every tick. Under prefers-reduced-motion the sprite holds
86
66
  // its track's first frame instead of animating (presentation-only; the
87
67
  // animation state machine is untouched).
88
- const spriteScale = display.size / FRAME_HEIGHT;
68
+ const spriteScale = display.size / cell.height;
89
69
  const animation = snapshot?.animation ?? 'idle';
90
70
  const scaleRef = useRef(spriteScale);
91
71
  scaleRef.current = spriteScale;
92
72
  useEffect(() => {
93
73
  const reduceMotion = typeof window !== 'undefined'
94
74
  && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true;
75
+ const row = rowOfTrack(animation);
76
+ const track = trimTrack(tracks[animation], rows[row] ?? tracks[animation].frames.length);
95
77
  // Paint one static sprite frame up front either way, so the pet is never
96
78
  // blank while the loop heat-up runs.
97
- const row = rowOfTrack(animation);
98
- const track = frameCounts === null
99
- ? TRACKS[animation]
100
- : trimTrack(TRACKS[animation], frameCounts[row] ?? TRACKS[animation].frames.length);
101
79
  const leadCol = track.frames[0];
102
- const lead = framePosition(row, leadCol, scaleRef.current);
80
+ const lead = framePosition(cell, columns, row, leadCol, scaleRef.current);
103
81
  if (spriteRef.current !== null) {
104
- spriteRef.current.style.backgroundPosition = `${lead.x}px ${lead.y}px`;
82
+ spriteRef.current.style.backgroundPosition = lead.x + 'px ' + lead.y + 'px';
105
83
  }
106
84
  if (reduceMotion)
107
85
  return;
@@ -113,9 +91,7 @@ export function WhalePet(props) {
113
91
  // Trim the track to the row's real frame count (transparent cells
114
92
  // would render as a vanishing pet).
115
93
  const row = rowOfTrack(animation);
116
- const track = frameCounts === null
117
- ? TRACKS[animation]
118
- : trimTrack(TRACKS[animation], frameCounts[row] ?? TRACKS[animation].frames.length);
94
+ const track = trimTrack(tracks[animation], rows[row] ?? tracks[animation].frames.length);
119
95
  const st = frameRef.current;
120
96
  if (st.track !== animation) {
121
97
  st.track = animation;
@@ -138,17 +114,17 @@ export function WhalePet(props) {
138
114
  }
139
115
  }
140
116
  const col = track.frames[st.index];
141
- const { x, y } = framePosition(row, col, scaleRef.current);
117
+ const pos = framePosition(cell, columns, row, col, scaleRef.current);
142
118
  if (spriteRef.current !== null) {
143
- spriteRef.current.style.backgroundPosition = `${x}px ${y}px`;
119
+ spriteRef.current.style.backgroundPosition = pos.x + 'px ' + pos.y + 'px';
144
120
  }
145
121
  raf = requestAnimationFrame(tick);
146
122
  };
147
123
  raf = requestAnimationFrame(tick);
148
124
  return () => cancelAnimationFrame(raf);
149
- }, [animation, frameCounts]);
125
+ }, [animation, cell, columns, rows, tracks]);
150
126
  // Auto-clear the feedback bubble after its CSS animation. The callback
151
- // rides a ref so re-renders never reset the timer: the 800ms poll rebuilds
127
+ // rides a ref so re-renders never reset the timer: the 2s poll rebuilds
152
128
  // `props` every tick, and depending on it would starve the timeout.
153
129
  const feedbackDoneRef = useRef(props.onFeedbackDone);
154
130
  feedbackDoneRef.current = props.onFeedbackDone;
@@ -160,7 +136,7 @@ export function WhalePet(props) {
160
136
  }, [feedback]);
161
137
  // Dragging: pointer events on the sprite; position is right/bottom based.
162
138
  // `draggedRef` records whether the pointer actually moved, so the browser's
163
- // trailing click (fired after pointerup) does not pet the whale.
139
+ // trailing click (fired after pointerup) does not pet the sprite.
164
140
  const draggedRef = useRef(false);
165
141
  const clearHideTimer = () => {
166
142
  if (hideTimerRef.current !== null) {
@@ -196,9 +172,10 @@ export function WhalePet(props) {
196
172
  props.onDragEnd(dragPos.right, dragPos.bottom);
197
173
  };
198
174
  const pos = dragPos ?? { right: display.right, bottom: display.bottom };
199
- const spriteWidth = Math.round(FRAME_WIDTH * spriteScale);
200
- const spriteHeight = Math.round(FRAME_HEIGHT * spriteScale);
175
+ const spriteWidth = Math.round(cell.width * spriteScale);
176
+ const spriteHeight = Math.round(cell.height * spriteScale);
201
177
  const statusBubble = feedback === null && !hovered ? snapshot?.bubble : undefined;
178
+ const displayName = snapshot?.name ?? definition.displayName;
202
179
  const float = (_jsxs("div", { ref: floatRef, className: styles.float, style: { right: pos.right, bottom: pos.bottom, zIndex: 2147483000 }, onPointerEnter: () => {
203
180
  clearHideTimer();
204
181
  setHovered(true);
@@ -208,7 +185,7 @@ export function WhalePet(props) {
208
185
  // container. Treat a target still inside the container's DOM (the
209
186
  // overflowed panel) as "still hovering"; otherwise give the pointer a
210
187
  // short grace period to reach the panel across the gap above it. The
211
- // bridge (`.panel::after`) keeps the pointer inside the hit area, and
188
+ // bridge ('.panel::after') keeps the pointer inside the hit area, and
212
189
  // the grace period covers a slow mouse crossing the remaining sliver.
213
190
  const next = e.relatedTarget;
214
191
  if (next instanceof Node && floatRef.current?.contains(next))
@@ -218,8 +195,8 @@ export function WhalePet(props) {
218
195
  }, children: [_jsx("div", { ref: spriteRef, className: styles.sprite, style: {
219
196
  width: spriteWidth,
220
197
  height: spriteHeight,
221
- backgroundImage: imageReady ? `url(${PET_SPRITESHEET_URL})` : undefined,
222
- backgroundSize: `${FRAME_WIDTH * FRAME_COLUMNS * spriteScale}px ${FRAME_HEIGHT * 9 * spriteScale}px`,
198
+ backgroundImage: imageReady ? 'url(' + definition.atlasUrl + ')' : undefined,
199
+ backgroundSize: (cell.width * columns * spriteScale) + 'px ' + (cell.height * rows.length * spriteScale) + 'px',
223
200
  backgroundRepeat: 'no-repeat',
224
201
  backgroundPosition: '0 0',
225
202
  cursor: dragRef.current === null ? 'grab' : 'grabbing',
@@ -229,7 +206,7 @@ export function WhalePet(props) {
229
206
  if (draggedRef.current)
230
207
  return;
231
208
  props.onPet();
232
- }, role: "button", "aria-label": "whale girl" }), feedback !== null && (_jsx("div", { className: `${styles.bubble} ${feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet}`, children: feedback.text }, feedback.at)), statusBubble !== undefined && (_jsx("div", { className: `${styles.bubble} ${styles.bubbleStatus}`, role: "status", "aria-live": "polite", children: statusBubble })), hovered && dragRef.current === null && (_jsx("div", { className: styles.panel, onPointerEnter: () => {
209
+ }, role: "button", "aria-label": definition.displayName }), feedback !== null && (_jsx("div", { className: clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet), children: feedback.text }, feedback.at)), statusBubble !== undefined && (_jsx("div", { className: clsx(styles.bubble, styles.bubbleStatus), role: "status", "aria-live": "polite", children: statusBubble })), hovered && dragRef.current === null && (_jsx("div", { className: styles.panel, onPointerEnter: () => {
233
210
  // Reaching the panel (or its bridge) must cancel any hide timer
234
211
  // the container's pointerleave may have armed while the pointer
235
212
  // crossed the sliver between the sprite and the panel.
@@ -257,8 +234,8 @@ export function WhalePet(props) {
257
234
  props.onRename(trimmed);
258
235
  setRenaming(false);
259
236
  }
260
- }, children: props.t('pet.confirm') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: snapshot?.name ?? '鲸鱼娘' }), _jsx("span", { children: props.t('pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { children: props.t('pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { children: props.t('pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: props.t('pet.feed') }), _jsx("button", { type: "button", className: styles.action, onClick: () => {
261
- setNameDraft(snapshot?.name ?? '');
237
+ }, children: props.t('pet.confirm') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { children: props.t('pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { children: props.t('pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { children: props.t('pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: props.t('pet.feed') }), _jsx("button", { type: "button", className: styles.action, onClick: () => {
238
+ setNameDraft(displayName);
262
239
  setRenaming(true);
263
240
  }, children: props.t('pet.rename') }), _jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: props.t('pet.hide') })] })] })) }))] }));
264
241
  return createPortal(float, document.body);
@@ -1,22 +1,27 @@
1
1
  /**
2
- * Shared chrome for the plugin settings card: a disclosure header naming the
3
- * plugin and what its settings govern, the controls inside, and the save that
4
- * writes them. Renders nothing while the namespace is unavailable — a
2
+ * Family-shared chrome for plugin settings cards: a disclosure header naming
3
+ * the plugin and what its settings govern, the controls inside, and the save
4
+ * that writes them. Renders nothing while the namespace is unavailable — a
5
5
  * deployment that does not compose the owning plugin should show no trace of
6
- * it. Mirrors the official ui-plugin-config PluginCard in a self-contained
7
- * slice (this package must not depend on a sibling UI package).
6
+ * it. Inlined into each consumer's client bundle; mirrors the official
7
+ * ui-plugin-config PluginCard in a self-contained slice.
8
8
  */
9
9
  import { type ReactNode } from 'react';
10
10
  import type { CardShell } from './settings-form.ts';
11
- import type { SettingsCardKey } from './locales.ts';
11
+ /** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
12
+ export declare const CARD_COPY_KEYS: readonly ["settings.collapse", "settings.expand", "settings.notExposed", "settings.unsaved", "settings.readOnly", "settings.saveFailed", "settings.discard", "settings.save", "settings.saving"];
13
+ /** Copy key the card chrome itself reads. */
14
+ export type CardCopyKey = (typeof CARD_COPY_KEYS)[number];
15
+ /** Key domain for the plugin's own copy (inferred per consumer). */
16
+ export type SettingsCardKey<TKey extends string = string> = TKey | CardCopyKey;
12
17
  /** Card chrome shared by every plugin settings card. */
13
- export interface PluginSettingsCardProps {
18
+ export interface PluginSettingsCardProps<TKey extends string = string> {
14
19
  /** Locale reader for this card's copy. */
15
- t: (key: SettingsCardKey) => string;
20
+ t: (key: SettingsCardKey<TKey>, params?: Record<string, string | number>) => string;
16
21
  /** Locale key of the plugin's name. */
17
- titleKey: SettingsCardKey;
22
+ titleKey: TKey;
18
23
  /** Locale key of the line describing what this plugin's settings govern. */
19
- descriptionKey: SettingsCardKey;
24
+ descriptionKey: TKey;
20
25
  /** The card's form state: availability, writability, and what a save would do. */
21
26
  state: CardShell;
22
27
  /** Write every staged edit. */
@@ -31,7 +36,7 @@ export interface PluginSettingsCardProps {
31
36
  * @param props - the plugin's copy keys, its form state, and its controls.
32
37
  * @returns the card, or nothing while the namespace is still loading.
33
38
  */
34
- export declare function PluginSettingsCard(props: PluginSettingsCardProps): import("react").JSX.Element | null;
39
+ export declare function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>): import("react").JSX.Element | null;
35
40
  /** Props every field control needs regardless of its value type. */
36
41
  export interface FieldProps {
37
42
  /** Stable id associating the label with its control. */
@@ -75,4 +80,14 @@ export declare function BooleanField(props: FieldProps & {
75
80
  /** Copy for the off option. */
76
81
  offLabel: string;
77
82
  }): import("react").JSX.Element;
83
+ /** A staged enumerated field rendered as a select. */
84
+ export declare function ChoiceField(props: FieldProps & {
85
+ /** Copy for the inherit option (draft text is the empty string). */
86
+ inheritLabel: string;
87
+ /** Choices rendered in order; `value` is the draft/stored text. */
88
+ choices: ReadonlyArray<{
89
+ value: string;
90
+ label: string;
91
+ }>;
92
+ }): import("react").JSX.Element;
78
93
  //# sourceMappingURL=PluginSettingsCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAGnD,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAA;IACnC,uCAAuC;IACvC,QAAQ,EAAE,eAAe,CAAA;IACzB,4EAA4E;IAC5E,cAAc,EAAE,eAAe,CAAA;IAC/B,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,sCA0GhE;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA"}
1
+ {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,sCAiHpG;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG;IAC9C,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD,+BAsCA"}
@@ -1,14 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Generated by scripts/sync-shared.mjs from shared/client/settings/PluginSettingsCard.tsx. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
3
  /**
3
- * Shared chrome for the plugin settings card: a disclosure header naming the
4
- * plugin and what its settings govern, the controls inside, and the save that
5
- * writes them. Renders nothing while the namespace is unavailable — a
4
+ * Family-shared chrome for plugin settings cards: a disclosure header naming
5
+ * the plugin and what its settings govern, the controls inside, and the save
6
+ * that writes them. Renders nothing while the namespace is unavailable — a
6
7
  * deployment that does not compose the owning plugin should show no trace of
7
- * it. Mirrors the official ui-plugin-config PluginCard in a self-contained
8
- * slice (this package must not depend on a sibling UI package).
8
+ * it. Inlined into each consumer's client bundle; mirrors the official
9
+ * ui-plugin-config PluginCard in a self-contained slice.
9
10
  */
10
11
  import { useState } from 'react';
11
12
  import css from './settings-card.module.css';
13
+ /** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
14
+ export const CARD_COPY_KEYS = [
15
+ 'settings.collapse',
16
+ 'settings.expand',
17
+ 'settings.notExposed',
18
+ 'settings.unsaved',
19
+ 'settings.readOnly',
20
+ 'settings.saveFailed',
21
+ 'settings.discard',
22
+ 'settings.save',
23
+ 'settings.saving',
24
+ ];
12
25
  /**
13
26
  * Render one plugin settings card.
14
27
  * @param props - the plugin's copy keys, its form state, and its controls.
@@ -20,6 +33,7 @@ export function PluginSettingsCard(props) {
20
33
  if (!state.available)
21
34
  return null;
22
35
  const title = props.t(props.titleKey);
36
+ const description = props.t(props.descriptionKey);
23
37
  const blocked = !state.dirty || state.invalid || state.saving;
24
38
  const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card;
25
39
  // The namespace exists but the Host does not serve it to this client (the
@@ -27,12 +41,14 @@ export function PluginSettingsCard(props) {
27
41
  // that explains the gap instead of vanishing, so a missing card never
28
42
  // reads as a missing plugin.
29
43
  if (!state.exposed) {
30
- return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, children: title }), _jsx("span", { className: css.description, children: props.t(props.descriptionKey) })] }), _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
44
+ return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
31
45
  ? (_jsx("div", { className: css.body, children: _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) }))
32
46
  : null] }));
33
47
  }
34
- return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, children: title }), _jsx("span", { className: css.description, children: props.t(props.descriptionKey) })] }), state.dirty ? _jsx("span", { className: css.pending, children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
35
- ? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed ? _jsx("p", { className: css.failed, role: "status", children: props.t('settings.saveFailed') }) : null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
48
+ return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
49
+ ? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed
50
+ ? (_jsxs("p", { className: css.failed, role: "status", children: [props.t('settings.saveFailed'), state.failedReason ? ' - ' + state.failedReason : ''] }))
51
+ : null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
36
52
  : null] }));
37
53
  }
38
54
  /** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */
@@ -47,3 +63,9 @@ export function BooleanField(props) {
47
63
  ? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
48
64
  : null] }), _jsxs("select", { id: props.id, className: css.select, value: props.text, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: [_jsx("option", { value: "", children: props.inheritLabel }), _jsx("option", { value: "true", children: props.onLabel }), _jsx("option", { value: "false", children: props.offLabel })] }), _jsx("p", { className: css.hint, children: props.hint })] }));
49
65
  }
66
+ /** A staged enumerated field rendered as a select. */
67
+ export function ChoiceField(props) {
68
+ return (_jsxs("div", { className: css.field, children: [_jsxs("div", { className: css.head, children: [_jsx("label", { className: css.label, htmlFor: props.id, children: props.label }), props.overridden
69
+ ? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
70
+ : null] }), _jsxs("select", { id: props.id, className: css.select, value: props.text, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: [_jsx("option", { value: "", children: props.inheritLabel }), props.choices.map(choice => (_jsx("option", { value: choice.value, children: choice.label }, choice.value)))] }), _jsx("p", { className: props.invalid ? css.invalid : css.hint, children: props.invalid ? props.invalidLabel : props.hint })] }));
71
+ }
@@ -1,11 +1,12 @@
1
1
  /**
2
- * dsh-pet browser half — mounts the whale-girl as a global floating surface
3
- * and drives it from the host's same-origin `/api/pet/*` JSON endpoints: poll
4
- * the host snapshot (~800 ms), forward interactions, persist drag positions.
5
- * The pet is host-global (no session dimension), so it mounts directly onto
6
- * `document.body` via a single React root rather than a session-scoped slot —
7
- * on the new-conversation screen no session exists, and a dock-mounted pet
8
- * would vanish there (issue #48). When the pet is hidden the entry becomes a
2
+ * dsh-pet browser half — mounts the selected pet as a global floating
3
+ * surface and drives it from the host's same-origin '/api/pet/*' JSON
4
+ * endpoints: fetch the registry list once, poll the host snapshot (~2 s),
5
+ * forward interactions, persist drag positions. The pet is host-global (no
6
+ * session dimension), so it mounts directly onto 'document.body' via a
7
+ * single React root rather than a session-scoped slot — on the
8
+ * new-conversation screen no session exists, and a dock-mounted pet would
9
+ * vanish there (issue #48). When the pet is hidden the entry becomes a
9
10
  * fixed-position summon button.
10
11
  * @module @linxin666/dsh-pet/client
11
12
  */
@@ -14,13 +15,15 @@ import type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-
14
15
  export declare const inject: string[];
15
16
  /** Re-exported for consumers that type against the injected face. */
16
17
  export type { PetInjected, PetDockEntryProps } from './PetDockEntry.tsx';
18
+ export type { PetSpriteProps } from './PetSprite.tsx';
17
19
  export type { PetUiState, PetFeedback } from './pet-store.ts';
18
20
  export type { PetSettingsCardFace, PetSettingsCardState } from './PetSettingsCard.tsx';
21
+ export type { PetDefinition } from '../registry.ts';
19
22
  declare module '@deepseek-ai/dsh-client-ui-slots' {
20
23
  interface SlotMap {
21
24
  /**
22
25
  * The child slot the Web UI plugin group declares; this card registers
23
- * into the group instead of the top-level `settings.plugin.item` list.
26
+ * into the group instead of the top-level 'settings.plugin.item' list.
24
27
  * Spelled here with the same shape so this package can register without
25
28
  * depending on the sibling UI package.
26
29
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAwD7G,yBAAyB;AACzB,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAEtF,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,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;AAGD;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoK9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AA6D7G,yBAAyB;AACzB,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF,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,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,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,CAmL9C"}
@@ -1,11 +1,12 @@
1
1
  /**
2
- * dsh-pet browser half — mounts the whale-girl as a global floating surface
3
- * and drives it from the host's same-origin `/api/pet/*` JSON endpoints: poll
4
- * the host snapshot (~800 ms), forward interactions, persist drag positions.
5
- * The pet is host-global (no session dimension), so it mounts directly onto
6
- * `document.body` via a single React root rather than a session-scoped slot —
7
- * on the new-conversation screen no session exists, and a dock-mounted pet
8
- * would vanish there (issue #48). When the pet is hidden the entry becomes a
2
+ * dsh-pet browser half — mounts the selected pet as a global floating
3
+ * surface and drives it from the host's same-origin '/api/pet/*' JSON
4
+ * endpoints: fetch the registry list once, poll the host snapshot (~2 s),
5
+ * forward interactions, persist drag positions. The pet is host-global (no
6
+ * session dimension), so it mounts directly onto 'document.body' via a
7
+ * single React root rather than a session-scoped slot — on the
8
+ * new-conversation screen no session exists, and a dock-mounted pet would
9
+ * vanish there (issue #48). When the pet is hidden the entry becomes a
9
10
  * fixed-position summon button.
10
11
  * @module @linxin666/dsh-pet/client
11
12
  */
@@ -25,20 +26,22 @@ async function petFetch(path, body) {
25
26
  body: JSON.stringify(body),
26
27
  });
27
28
  if (!response.ok) {
28
- throw new Error(`pet ${path} failed: ${response.status}`);
29
+ throw new Error('pet ' + path + ' failed: ' + response.status);
29
30
  }
30
31
  return (await response.json());
31
32
  }
32
33
  /** The live host API instance (always defined; failures surface per call). */
33
34
  const petApi = {
34
35
  state: () => petFetch('/api/pet/state'),
36
+ pets: () => petFetch('/api/pet/pets'),
35
37
  interact: (kind) => petFetch('/api/pet/interact', { kind }),
36
38
  setVisible: (visible) => petFetch('/api/pet/set-visible', { visible }),
37
39
  setConfig: (patch) => petFetch('/api/pet/set-config', patch),
38
40
  setName: (name) => petFetch('/api/pet/set-name', { name }),
41
+ setPet: (petId) => petFetch('/api/pet/set-pet', { petId }),
39
42
  };
40
43
  /** Poll interval for the host snapshot. */
41
- const POLL_MS = 800;
44
+ const POLL_MS = 2000;
42
45
  /** Settings namespace the pet settings card edits (the Host plugin registers it). */
43
46
  const PET_SETTINGS_NS = 'pet';
44
47
  /** Required services. */
@@ -59,8 +62,9 @@ export function apply(ctx) {
59
62
  ? snapshot.value?.enabled ?? true
60
63
  : snapshot.status === 'unavailable';
61
64
  };
62
- // Plugin configuration card: one staged form over the `pet` settings
63
- // namespace, contributed to the Web UI plugin group.
65
+ // Plugin configuration card: one staged form over the 'pet' settings
66
+ // namespace, contributed to the Web UI plugin group. The controller loads
67
+ // the petId choices from the registry endpoint itself.
64
68
  const petSettings = new PetSettingsCardController(settingsScope);
65
69
  ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({
66
70
  name: 'web-ui.plugin.item',
@@ -81,9 +85,23 @@ export function apply(ctx) {
81
85
  // it stateless on the new-conversation screen (no session to scope by).
82
86
  const petStore = createPetStore().create();
83
87
  const setSnapshot = petStore.actions.setSnapshot;
88
+ const setPets = petStore.actions.setPets;
84
89
  const setState = petStore.actions.setState;
85
90
  const setFeedback = petStore.actions.setFeedback;
91
+ // The registry list is fetched lazily with retries baked into the poll
92
+ // cycle: until it lands, the dock entry renders nothing and every 2s
93
+ // tick tries again. After it lands, one list feeds both the sprite and
94
+ // the settings card's choices.
95
+ let petsLoaded = false;
86
96
  const pollNow = () => {
97
+ if (!petsLoaded) {
98
+ petApi.pets().then((list) => {
99
+ petsLoaded = true;
100
+ setPets(list);
101
+ }, () => {
102
+ // Retry on the next poll tick.
103
+ });
104
+ }
87
105
  petApi.state().then((snapshot) => {
88
106
  setSnapshot(snapshot);
89
107
  }, () => {
@@ -95,7 +113,7 @@ export function apply(ctx) {
95
113
  // change while the page is hidden, so a background interval would
96
114
  // only burn RPCs (browser throttling is an unreliable backstop).
97
115
  // Coming back to the tab refreshes the pet immediately instead of
98
- // waiting out the next 800 ms cycle.
116
+ // waiting out the next 2 s cycle.
99
117
  let timer;
100
118
  const stop = () => {
101
119
  if (timer !== undefined) {
@@ -187,8 +205,8 @@ export function apply(ctx) {
187
205
  // for a global floating surface — the dock is session-scoped, so a pet
188
206
  // mounted there would vanish on the new-conversation screen (issue #48).
189
207
  // The entry therefore mounts straight onto document.body via a single
190
- // React root for the page lifetime: WhalePet portals itself to body when
191
- // visible, and the hidden-state summon button is fixed-positioned.
208
+ // React root for the page lifetime: PetSprite portals itself to body
209
+ // when visible, and the hidden-state summon button is fixed-positioned.
192
210
  const container = document.createElement('div');
193
211
  container.dataset.dshPetRoot = '';
194
212
  document.body.appendChild(container);
@@ -15,10 +15,12 @@ export declare const zh: {
15
15
  readonly 'pet.rank': "亲密度 {rank}";
16
16
  readonly 'pet.points': "{points} 点";
17
17
  readonly 'pet.treats': "小鱼干 ×{n}";
18
- readonly 'pet.state.loading': "鲸鱼娘正在赶来…";
19
- readonly 'pet.state.error': "鲸鱼娘迷路了(连接失败)";
18
+ readonly 'pet.state.loading': "宠物正在赶来…";
19
+ readonly 'pet.state.error': "宠物迷路了(连接失败)";
20
20
  readonly 'settings.title': "宠物";
21
- readonly 'settings.description': "鲸鱼娘的显示布局与名字。";
21
+ readonly 'settings.description': "选择宠物并调整它的显示布局。";
22
+ readonly 'settings.pet': "宠物";
23
+ readonly 'settings.petHint': "选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。";
22
24
  readonly 'settings.enabled': "启用宠物";
23
25
  readonly 'settings.enabledHint': "关闭后隐藏宠物并停止轮询,可在设置里重新启用。";
24
26
  readonly 'settings.visible': "显示宠物";
@@ -29,8 +31,6 @@ export declare const zh: {
29
31
  readonly 'settings.rightHint': "距视口右边缘的水平内缩距离。";
30
32
  readonly 'settings.bottom': "距底部(px)";
31
33
  readonly 'settings.bottomHint': "距视口底边的垂直内缩距离。";
32
- readonly 'settings.name': "名字";
33
- readonly 'settings.nameHint': "宠物显示名,1–20 个字符。";
34
34
  readonly 'settings.inherit': "继承";
35
35
  readonly 'settings.on': "开";
36
36
  readonly 'settings.off': "关";
@@ -58,10 +58,12 @@ export declare const en: {
58
58
  readonly 'pet.rank': "Affinity {rank}";
59
59
  readonly 'pet.points': "{points} pts";
60
60
  readonly 'pet.treats': "Treats ×{n}";
61
- readonly 'pet.state.loading': "The whale girl is on her way…";
62
- readonly 'pet.state.error': "The whale girl is lost (connection failed)";
61
+ readonly 'pet.state.loading': "The pet is on its way…";
62
+ readonly 'pet.state.error': "The pet is lost (connection failed)";
63
63
  readonly 'settings.title': "Pet";
64
- readonly 'settings.description': "The whale girl’s display layout and name.";
64
+ readonly 'settings.description': "Pick a pet and tune its display layout.";
65
+ readonly 'settings.pet': "Pet";
66
+ readonly 'settings.petHint': "Choose which pet shows. Names are stored per pet; rename from the pet hover panel.";
65
67
  readonly 'settings.enabled': "Enable the pet";
66
68
  readonly 'settings.enabledHint': "When off, the pet hides and polling stops; re-enable it here.";
67
69
  readonly 'settings.visible': "Show the pet";
@@ -72,8 +74,6 @@ export declare const en: {
72
74
  readonly 'settings.rightHint': "Horizontal inset from the viewport right edge.";
73
75
  readonly 'settings.bottom': "Bottom inset (px)";
74
76
  readonly 'settings.bottomHint': "Vertical inset from the viewport bottom edge.";
75
- readonly 'settings.name': "Name";
76
- readonly 'settings.nameHint': "The pet’s display name, 1–20 characters.";
77
77
  readonly 'settings.inherit': "Inherit";
78
78
  readonly 'settings.on': "On";
79
79
  readonly 'settings.off': "Off";