@linxin666/dsh-pet 0.1.13 → 0.1.15

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 (57) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +11 -7
  3. package/README.zh.md +11 -7
  4. package/lib/client.js +38 -17
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +393 -150
  7. package/lib/invariant.js +1 -1
  8. package/lib/{state-C9EyycwI.js → state-CFyJv0sQ.js} +22 -7
  9. package/lib/types/affinity.d.ts +15 -0
  10. package/lib/types/affinity.d.ts.map +1 -1
  11. package/lib/types/affinity.js +14 -0
  12. package/lib/types/client/PluginSettingsCard.d.ts +26 -11
  13. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/PluginSettingsCard.js +27 -7
  15. package/lib/types/client/WhalePet.d.ts.map +1 -1
  16. package/lib/types/client/WhalePet.js +2 -1
  17. package/lib/types/client/index.d.ts +1 -1
  18. package/lib/types/client/index.js +3 -3
  19. package/lib/types/client/settings-form.d.ts +14 -5
  20. package/lib/types/client/settings-form.d.ts.map +1 -1
  21. package/lib/types/client/settings-form.js +38 -10
  22. package/lib/types/dsh-home.d.ts +17 -0
  23. package/lib/types/dsh-home.d.ts.map +1 -0
  24. package/lib/types/dsh-home.js +34 -0
  25. package/lib/types/event-projection.d.ts +36 -0
  26. package/lib/types/event-projection.d.ts.map +1 -0
  27. package/lib/types/event-projection.js +98 -0
  28. package/lib/types/ledger.d.ts +77 -0
  29. package/lib/types/ledger.d.ts.map +1 -0
  30. package/lib/types/ledger.js +139 -0
  31. package/lib/types/persist.d.ts +5 -1
  32. package/lib/types/persist.d.ts.map +1 -1
  33. package/lib/types/persist.js +7 -3
  34. package/lib/types/service.d.ts +24 -44
  35. package/lib/types/service.d.ts.map +1 -1
  36. package/lib/types/service.js +98 -131
  37. package/lib/types/state.d.ts +10 -12
  38. package/lib/types/state.d.ts.map +1 -1
  39. package/lib/types/state.js +14 -10
  40. package/package.json +1 -1
  41. package/src/affinity.ts +33 -0
  42. package/src/client/PluginSettingsCard.tsx +83 -17
  43. package/src/client/WhalePet.test.tsx +25 -1
  44. package/src/client/WhalePet.tsx +6 -0
  45. package/src/client/index.ts +3 -3
  46. package/src/client/pet.module.css +9 -0
  47. package/src/client/settings-card.module.css +6 -0
  48. package/src/client/settings-form.ts +41 -9
  49. package/src/dsh-home.test.ts +35 -0
  50. package/src/dsh-home.ts +36 -0
  51. package/src/event-projection.ts +128 -0
  52. package/src/ledger.test.ts +67 -0
  53. package/src/ledger.ts +183 -0
  54. package/src/persist.ts +7 -3
  55. package/src/service.ts +110 -171
  56. package/src/state.test.ts +4 -1
  57. package/src/state.ts +17 -13
@@ -48,6 +48,20 @@ function rankOf(points) {
48
48
  for (const candidate of AFFINITY_RANKS) if (points >= candidate.min) rank = candidate;
49
49
  return rank;
50
50
  }
51
+ /** Derive the read-only view of one affinity state at a wall-clock instant. */
52
+ function affinityViewOf(state, nowMs, config = defaultAffinityConfig) {
53
+ const rank = rankOf(state.points);
54
+ return {
55
+ points: state.points,
56
+ rank: rank.name,
57
+ rankEmoji: rank.emoji,
58
+ pets: state.pets,
59
+ feeds: state.feeds,
60
+ turns: state.turns,
61
+ petCooldown: nowMs - state.lastPetAt < config.petCooldownMs,
62
+ feedCooldown: nowMs - state.lastFeedAt < config.feedCooldownMs
63
+ };
64
+ }
51
65
  function clamp(points) {
52
66
  return Math.min(100, Math.max(0, points));
53
67
  }
@@ -112,20 +126,21 @@ function applyTurnReward(state, config = defaultAffinityConfig) {
112
126
  const defaultPetStateConfig = { celebrateMs: 2400 };
113
127
  /**
114
128
  * Map one activity phase onto the animation contract.
115
- * - thinking / tool → `running` (focused work), with `running-right` as the
116
- * side-alternating variant the client may use for tool activity.
129
+ * - thinking → `running` and tool `running-right` (focused work).
130
+ * - review `review` while answer text is streaming.
117
131
  * - waiting → `waiting` (expectant pose, needs user input).
118
132
  * - done → `jumping` (celebration), then back to `idle` after the window.
133
+ * - failed → `failed` until another activity event arrives.
119
134
  * - idle → `idle` (calm breathing loop).
120
- * `failed` has no DSH phase source yet; the machine keeps the mapping table
121
- * so a future error event can light it up.
122
135
  */
123
136
  function animationForPhase(phase) {
124
137
  switch (phase) {
125
138
  case "thinking": return "running";
126
139
  case "tool": return "running-right";
140
+ case "review": return "review";
127
141
  case "waiting": return "waiting";
128
142
  case "done": return "jumping";
143
+ case "failed": return "failed";
129
144
  case "idle": return "idle";
130
145
  }
131
146
  }
@@ -159,7 +174,7 @@ var PetStateMachine = class {
159
174
  this.config = config;
160
175
  this.now = now;
161
176
  }
162
- /** Consume one phase snapshot (fed by the service from session events). */
177
+ /** Consume one projected activity update. */
163
178
  onActivityStatus(input) {
164
179
  this.phase = input.phase;
165
180
  this.line = input.line;
@@ -184,7 +199,7 @@ var PetStateMachine = class {
184
199
  let animation = animationForPhase(this.phase);
185
200
  if (this.phase === "done" && this.doneAt !== void 0) if (nowMs - this.doneAt < this.config.celebrateMs) animation = "jumping";
186
201
  else animation = "idle";
187
- const bubble = this.phrase ?? this.line;
202
+ const bubble = this.phase === "done" && this.doneAt !== void 0 && nowMs - this.doneAt >= this.config.celebrateMs ? void 0 : this.phrase ?? this.line;
188
203
  return {
189
204
  animation,
190
205
  ...bubble === void 0 ? {} : { bubble },
@@ -195,4 +210,4 @@ var PetStateMachine = class {
195
210
  }
196
211
  };
197
212
  //#endregion
198
- export { AFFINITY_MAX as a, applyTurnReward as c, rankOf as d, rowOf as i, defaultAffinityConfig as l, animationForPhase as n, AFFINITY_RANKS as o, defaultPetStateConfig as r, applyInteraction as s, PetStateMachine as t, emptyAffinity as u };
213
+ export { AFFINITY_MAX as a, applyInteraction as c, emptyAffinity as d, rankOf as f, rowOf as i, applyTurnReward as l, animationForPhase as n, AFFINITY_RANKS as o, defaultPetStateConfig as r, affinityViewOf as s, PetStateMachine as t, defaultAffinityConfig as u };
@@ -71,6 +71,21 @@ export interface InteractionOutcome {
71
71
  }
72
72
  /** Rank for a point total. */
73
73
  export declare function rankOf(points: number): (typeof AFFINITY_RANKS)[number];
74
+ /** Read-only affinity snapshot suited for the RPC view shape. */
75
+ export interface PetAffinityView {
76
+ points: number;
77
+ rank: string;
78
+ rankEmoji: string;
79
+ pets: number;
80
+ feeds: number;
81
+ turns: number;
82
+ /** True while the pet interaction is inside its cooldown. */
83
+ petCooldown: boolean;
84
+ /** True while the feed is inside its cooldown. */
85
+ feedCooldown: boolean;
86
+ }
87
+ /** Derive the read-only view of one affinity state at a wall-clock instant. */
88
+ export declare function affinityViewOf(state: AffinityState, nowMs: number, config?: AffinityConfig): PetAffinityView;
74
89
  /**
75
90
  * Apply one interaction to a copy of the state (immutable style: returns a
76
91
  * new object; the caller replaces the persisted state). Cooldowns only
@@ -1 +1 @@
1
- {"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B;;iEAEiE;AACjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAKjB,CAAA;AAEV,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAE7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA+BpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
1
+ {"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B;;iEAEiE;AACjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAKjB,CAAA;AAEV,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAE7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,eAAe,CAYjB;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA+BpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
@@ -34,6 +34,20 @@ export function rankOf(points) {
34
34
  }
35
35
  return rank;
36
36
  }
37
+ /** Derive the read-only view of one affinity state at a wall-clock instant. */
38
+ export function affinityViewOf(state, nowMs, config = defaultAffinityConfig) {
39
+ const rank = rankOf(state.points);
40
+ return {
41
+ points: state.points,
42
+ rank: rank.name,
43
+ rankEmoji: rank.emoji,
44
+ pets: state.pets,
45
+ feeds: state.feeds,
46
+ turns: state.turns,
47
+ petCooldown: nowMs - state.lastPetAt < config.petCooldownMs,
48
+ feedCooldown: nowMs - state.lastFeedAt < config.feedCooldownMs,
49
+ };
50
+ }
37
51
  function clamp(points) {
38
52
  return Math.min(AFFINITY_MAX, Math.max(0, points));
39
53
  }
@@ -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,sCA2GpG;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,11 +41,11 @@ 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
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
35
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 ? _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') })] })] }))
36
50
  : null] }));
37
51
  }
@@ -47,3 +61,9 @@ export function BooleanField(props) {
47
61
  ? (_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
62
  : 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
63
  }
64
+ /** A staged enumerated field rendered as a select. */
65
+ export function ChoiceField(props) {
66
+ 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
67
+ ? (_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 })] }))
68
+ : 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 })] }));
69
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"WhalePet.d.ts","sourceRoot":"","sources":["../../../src/client/WhalePet.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAqC,WAAW,EAAE,MAAM,OAAO,CAAA;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,iFAAiF;AACjF,eAAO,MAAM,mBAAmB,gCAAgC,CAAA;AAEhE,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,wBAAwB,CAAA;AAErD,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAOD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW,CAmT1D"}
1
+ {"version":3,"file":"WhalePet.d.ts","sourceRoot":"","sources":["../../../src/client/WhalePet.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAqC,WAAW,EAAE,MAAM,OAAO,CAAA;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,iFAAiF;AACjF,eAAO,MAAM,mBAAmB,gCAAgC,CAAA;AAEhE,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,wBAAwB,CAAA;AAErD,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAOD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW,CAyT1D"}
@@ -198,6 +198,7 @@ export function WhalePet(props) {
198
198
  const pos = dragPos ?? { right: display.right, bottom: display.bottom };
199
199
  const spriteWidth = Math.round(FRAME_WIDTH * spriteScale);
200
200
  const spriteHeight = Math.round(FRAME_HEIGHT * spriteScale);
201
+ const statusBubble = feedback === null && !hovered ? snapshot?.bubble : undefined;
201
202
  const float = (_jsxs("div", { ref: floatRef, className: styles.float, style: { right: pos.right, bottom: pos.bottom, zIndex: 2147483000 }, onPointerEnter: () => {
202
203
  clearHideTimer();
203
204
  setHovered(true);
@@ -228,7 +229,7 @@ export function WhalePet(props) {
228
229
  if (draggedRef.current)
229
230
  return;
230
231
  props.onPet();
231
- }, 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)), hovered && dragRef.current === null && (_jsx("div", { className: styles.panel, onPointerEnter: () => {
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: () => {
232
233
  // Reaching the panel (or its bridge) must cancel any hide timer
233
234
  // the container's pointerleave may have armed while the pointer
234
235
  // crossed the sliver between the sprite and the panel.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * dsh-pet browser half — mounts the whale-girl as a global floating surface
3
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.
4
+ * the host snapshot (~2 s), forward interactions, persist drag positions.
5
5
  * The pet is host-global (no session dimension), so it mounts directly onto
6
6
  * `document.body` via a single React root rather than a session-scoped slot —
7
7
  * on the new-conversation screen no session exists, and a dock-mounted pet
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * dsh-pet browser half — mounts the whale-girl as a global floating surface
3
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.
4
+ * the host snapshot (~2 s), forward interactions, persist drag positions.
5
5
  * The pet is host-global (no session dimension), so it mounts directly onto
6
6
  * `document.body` via a single React root rather than a session-scoped slot —
7
7
  * on the new-conversation screen no session exists, and a dock-mounted pet
@@ -38,7 +38,7 @@ const petApi = {
38
38
  setName: (name) => petFetch('/api/pet/set-name', { name }),
39
39
  };
40
40
  /** Poll interval for the host snapshot. */
41
- const POLL_MS = 800;
41
+ const POLL_MS = 2000;
42
42
  /** Settings namespace the pet settings card edits (the Host plugin registers it). */
43
43
  const PET_SETTINGS_NS = 'pet';
44
44
  /** Required services. */
@@ -95,7 +95,7 @@ export function apply(ctx) {
95
95
  // change while the page is hidden, so a background interval would
96
96
  // only burn RPCs (browser throttling is an unreliable backstop).
97
97
  // Coming back to the tab refreshes the pet immediately instead of
98
- // waiting out the next 800 ms cycle.
98
+ // waiting out the next 2 s cycle.
99
99
  let timer;
100
100
  const stop = () => {
101
101
  if (timer !== undefined) {
@@ -2,9 +2,9 @@
2
2
  * Staged form model behind the plugin settings card. A card stages what the
3
3
  * user types and writes it only when they save — the settings write is a
4
4
  * durable, revision-fenced document mutation, so staging keeps what is on
5
- * screen exactly what a save would store. Mirrors the official
6
- * ui-plugin-config card-store pattern in a self-contained slice: this
7
- * package must not depend on a sibling UI package.
5
+ * screen exactly what a save would store. Family-shared slice inlined into
6
+ * each plugin's client bundle; mirrors the official ui-plugin-config
7
+ * card-store pattern.
8
8
  */
9
9
  import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
10
  /** The write one field's staged text performs when the card is saved. */
@@ -69,12 +69,21 @@ export interface CardActions {
69
69
  /** Drop every staged edit. */
70
70
  discard: () => void;
71
71
  }
72
- /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
73
- export declare function numberField(field: string): FieldSpec;
72
+ /** Constraints a numeric field's accepted drafts must satisfy, mirroring the host schema. */
73
+ export interface NumberConstraints {
74
+ /** The accepted value must be a whole number. */
75
+ integer?: boolean;
76
+ /** The accepted value must be at least this. */
77
+ min?: number;
78
+ }
79
+ /** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
80
+ export declare function numberField(field: string, constraints?: NumberConstraints): FieldSpec;
74
81
  /** A free-text field. An empty draft clears the field. */
75
82
  export declare function textField(field: string): FieldSpec;
76
83
  /** A boolean field, edited through true/false draft text. */
77
84
  export declare function booleanField(field: string): FieldSpec;
85
+ /** An enumerated string field; only the listed choices are accepted. An empty draft clears the field. */
86
+ export declare function choiceField(field: string, choices: readonly string[]): FieldSpec;
78
87
  /**
79
88
  * Stages one card's edits over one settings namespace and writes them on save.
80
89
  *
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,16 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/client/settings/settings-form.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
1
2
  /**
2
3
  * Staged form model behind the plugin settings card. A card stages what the
3
4
  * user types and writes it only when they save — the settings write is a
4
5
  * durable, revision-fenced document mutation, so staging keeps what is on
5
- * screen exactly what a save would store. Mirrors the official
6
- * ui-plugin-config card-store pattern in a self-contained slice: this
7
- * package must not depend on a sibling UI package.
6
+ * screen exactly what a save would store. Family-shared slice inlined into
7
+ * each plugin's client bundle; mirrors the official ui-plugin-config
8
+ * card-store pattern.
8
9
  */
9
10
  import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
- /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
11
- export function numberField(field) {
11
+ /** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
12
+ export function numberField(field, constraints = {}) {
13
+ const { integer = false, min } = constraints;
12
14
  return {
13
15
  field,
14
16
  format: value => typeof value === 'number' ? String(value) : '',
@@ -17,7 +19,13 @@ export function numberField(field) {
17
19
  if (trimmed === '')
18
20
  return { kind: 'clear' };
19
21
  const parsed = Number(trimmed);
20
- return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : undefined;
22
+ if (!Number.isFinite(parsed))
23
+ return undefined;
24
+ if (integer && !Number.isInteger(parsed))
25
+ return undefined;
26
+ if (min !== undefined && parsed < min)
27
+ return undefined;
28
+ return { kind: 'set', value: parsed };
21
29
  },
22
30
  };
23
31
  }
@@ -38,14 +46,29 @@ export function booleanField(field) {
38
46
  field,
39
47
  format: value => typeof value === 'boolean' ? String(value) : '',
40
48
  parse: (text) => {
41
- if (text === 'true')
49
+ const trimmed = text.trim();
50
+ if (trimmed === '')
51
+ return { kind: 'clear' };
52
+ if (trimmed === 'true')
42
53
  return { kind: 'set', value: true };
43
- if (text === 'false')
54
+ if (trimmed === 'false')
44
55
  return { kind: 'set', value: false };
45
56
  return undefined;
46
57
  },
47
58
  };
48
59
  }
60
+ /** An enumerated string field; only the listed choices are accepted. An empty draft clears the field. */
61
+ export function choiceField(field, choices) {
62
+ return {
63
+ field,
64
+ format: value => typeof value === 'string' && choices.includes(value) ? value : '',
65
+ parse: (text) => {
66
+ if (text === '')
67
+ return { kind: 'clear' };
68
+ return choices.includes(text) ? { kind: 'set', value: text } : undefined;
69
+ },
70
+ };
71
+ }
49
72
  /**
50
73
  * Stages one card's edits over one settings namespace and writes them on save.
51
74
  *
@@ -127,6 +150,9 @@ export class CardForm {
127
150
  const writes = plan.flatMap(item => item.run === undefined ? [] : [item.run]);
128
151
  if (plan.length === 0 || this.saving || writes.length !== plan.length)
129
152
  return;
153
+ // Snapshot the fields this save writes, so edits staged while it is in
154
+ // flight survive: only the staged keys this save actually wrote are cleared.
155
+ const fields = new Set(plan.map(item => item.field));
130
156
  this.saving = true;
131
157
  this.failed = false;
132
158
  this.publish();
@@ -134,8 +160,10 @@ export class CardForm {
134
160
  for (const write of writes) {
135
161
  landed = await write() && landed;
136
162
  }
137
- if (landed)
138
- this.staged.clear();
163
+ if (landed) {
164
+ for (const field of fields)
165
+ this.staged.delete(field);
166
+ }
139
167
  this.saving = false;
140
168
  this.failed = !landed;
141
169
  this.publish();
@@ -0,0 +1,17 @@
1
+ /**
2
+ * DSH_HOME resolution shared by the plugin family's Host halves: the
3
+ * environment override wins, the platform home fallback follows. Mirrors
4
+ * what dsh-pet and dsh-liangshen each used to implement locally.
5
+ */
6
+ /** Expand a leading ~ (or ~user) in a path, platform-style. */
7
+ export declare function expandHome(path: string, home?: string): string;
8
+ /**
9
+ * Resolve the DSH home directory.
10
+ * @param env - process environment to read DSH_HOME from.
11
+ * @param home - platform home directory fallback (test seam).
12
+ * @returns the absolute DSH home path.
13
+ */
14
+ export declare function resolveDshHome(env?: NodeJS.ProcessEnv, home?: string): string;
15
+ /** Resolve the DSH home directory from the live environment. */
16
+ export declare function dshHome(): string;
17
+ //# sourceMappingURL=dsh-home.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsh-home.d.ts","sourceRoot":"","sources":["../../src/dsh-home.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAKH,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAIzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAOrG;AAED,gEAAgE;AAChE,wBAAgB,OAAO,IAAI,MAAM,CAEhC"}
@@ -0,0 +1,34 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/host/dsh-home.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * DSH_HOME resolution shared by the plugin family's Host halves: the
4
+ * environment override wins, the platform home fallback follows. Mirrors
5
+ * what dsh-pet and dsh-liangshen each used to implement locally.
6
+ */
7
+ import { homedir } from 'node:os';
8
+ import { isAbsolute, join } from 'node:path';
9
+ /** Expand a leading ~ (or ~user) in a path, platform-style. */
10
+ export function expandHome(path, home = homedir()) {
11
+ if (path === '~')
12
+ return home;
13
+ if (path.startsWith('~/') || path.startsWith('~\\'))
14
+ return join(home, path.slice(2));
15
+ return path;
16
+ }
17
+ /**
18
+ * Resolve the DSH home directory.
19
+ * @param env - process environment to read DSH_HOME from.
20
+ * @param home - platform home directory fallback (test seam).
21
+ * @returns the absolute DSH home path.
22
+ */
23
+ export function resolveDshHome(env = process.env, home = homedir()) {
24
+ const raw = env.DSH_HOME;
25
+ if (raw !== undefined && raw.trim() !== '') {
26
+ const expanded = expandHome(raw.trim(), home);
27
+ return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded);
28
+ }
29
+ return join(home, '.dsh');
30
+ }
31
+ /** Resolve the DSH home directory from the live environment. */
32
+ export function dshHome() {
33
+ return resolveDshHome();
34
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Official session event projection — pure. Maps the durable DSH session
3
+ * vocabulary onto the pet's visual phases and carries an optional completed-
4
+ * turn reward for the ledger. Holds no state of its own; callers keep a
5
+ * {@link ProjectionRuntime} per session and feed events in arrival order.
6
+ * @module @linxin666/dsh-pet/event-projection
7
+ */
8
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
9
+ import type { PetStateInput } from './state.ts';
10
+ /** Runtime shape of the optional legacy activity event. */
11
+ export interface ActivityStatusEventLike {
12
+ phase?: string;
13
+ line?: string;
14
+ phrase?: string;
15
+ }
16
+ /** Per-session facts needed to project the official event stream. */
17
+ export interface ProjectionRuntime {
18
+ activeTools: Set<string>;
19
+ officialEventsSeen: boolean;
20
+ stepHadFailure: boolean;
21
+ }
22
+ /** One official event projection, optionally carrying a completed turn reward. */
23
+ export interface PetActivityTransition {
24
+ input: PetStateInput;
25
+ completedTurn?: number;
26
+ }
27
+ /** Fresh projection runtime for a newly seen session. */
28
+ export declare function emptyProjectionRuntime(): ProjectionRuntime;
29
+ /** Whether a legacy phase is part of the pet's supported vocabulary. */
30
+ export declare function isActivityPhase(phase: string): phase is PetStateInput['phase'];
31
+ /**
32
+ * Project the durable DSH session vocabulary into the pet's visual phases.
33
+ * Unknown and log-only events do not disturb the last meaningful activity.
34
+ */
35
+ export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime): PetActivityTransition | undefined;
36
+ //# sourceMappingURL=event-projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,yDAAyD;AACzD,wBAAgB,sBAAsB,IAAI,iBAAiB,CAE1D;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,GACzB,qBAAqB,GAAG,SAAS,CAyEnC"}