@linxin666/dsh-pet 0.1.15 → 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 (74) 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 +270 -334
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +641 -87
  9. package/lib/types/client/PetDockEntry.d.ts +14 -12
  10. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  11. package/lib/types/client/PetDockEntry.js +13 -8
  12. package/lib/types/client/PetSettingsCard.d.ts +22 -9
  13. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/PetSettingsCard.js +42 -7
  15. package/lib/types/client/{WhalePet.d.ts → PetSprite.d.ts} +21 -20
  16. package/lib/types/client/PetSprite.d.ts.map +1 -0
  17. package/lib/types/client/{WhalePet.js → PetSprite.js} +44 -67
  18. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  19. package/lib/types/client/PluginSettingsCard.js +3 -1
  20. package/lib/types/client/index.d.ts +11 -8
  21. package/lib/types/client/index.d.ts.map +1 -1
  22. package/lib/types/client/index.js +30 -12
  23. package/lib/types/client/locales.d.ts +10 -10
  24. package/lib/types/client/locales.js +10 -10
  25. package/lib/types/client/pet-store.d.ts +5 -0
  26. package/lib/types/client/pet-store.d.ts.map +1 -1
  27. package/lib/types/client/pet-store.js +4 -0
  28. package/lib/types/client/settings-form.d.ts +62 -0
  29. package/lib/types/client/settings-form.d.ts.map +1 -1
  30. package/lib/types/client/settings-form.js +61 -12
  31. package/lib/types/client/spritesheet.d.ts +14 -48
  32. package/lib/types/client/spritesheet.d.ts.map +1 -1
  33. package/lib/types/client/spritesheet.js +13 -81
  34. package/lib/types/index.d.ts +24 -11
  35. package/lib/types/index.d.ts.map +1 -1
  36. package/lib/types/index.js +53 -33
  37. package/lib/types/ledger.d.ts +4 -2
  38. package/lib/types/ledger.d.ts.map +1 -1
  39. package/lib/types/ledger.js +11 -4
  40. package/lib/types/persist.d.ts +10 -3
  41. package/lib/types/persist.d.ts.map +1 -1
  42. package/lib/types/persist.js +32 -5
  43. package/lib/types/registry.d.ts +158 -0
  44. package/lib/types/registry.d.ts.map +1 -0
  45. package/lib/types/registry.js +276 -0
  46. package/lib/types/routes.d.ts +10 -10
  47. package/lib/types/routes.d.ts.map +1 -1
  48. package/lib/types/routes.js +169 -51
  49. package/lib/types/service.d.ts +46 -13
  50. package/lib/types/service.d.ts.map +1 -1
  51. package/lib/types/service.js +73 -16
  52. package/package.json +2 -2
  53. package/src/client/PetDockEntry.tsx +34 -26
  54. package/src/client/PetSettingsCard.tsx +67 -22
  55. package/src/client/{WhalePet.test.tsx → PetSprite.test.tsx} +56 -8
  56. package/src/client/{WhalePet.tsx → PetSprite.tsx} +55 -73
  57. package/src/client/PluginSettingsCard.tsx +7 -1
  58. package/src/client/index.ts +36 -14
  59. package/src/client/locales.ts +10 -10
  60. package/src/client/pet-store.ts +9 -0
  61. package/src/client/settings-card.module.css +3 -0
  62. package/src/client/settings-form.ts +114 -12
  63. package/src/client/spritesheet.ts +16 -92
  64. package/src/index.test.ts +23 -0
  65. package/src/index.ts +89 -45
  66. package/src/ledger.test.ts +5 -3
  67. package/src/ledger.ts +11 -4
  68. package/src/persist.test.ts +52 -3
  69. package/src/persist.ts +42 -8
  70. package/src/registry.test.ts +132 -0
  71. package/src/registry.ts +400 -0
  72. package/src/routes.ts +166 -53
  73. package/src/service.ts +102 -20
  74. package/lib/types/client/WhalePet.d.ts.map +0 -1
package/src/service.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  * the pure event projection (`event-projection`) onto the state machine,
4
4
  * delegates the affinity economy to the ledger (`ledger`), and routes
5
5
  * persistence through `persist`. The API gateway maps these methods onto
6
- * `pet.state` / `pet.interact` / `pet.setVisible` / `pet.setConfig` for
7
- * browser consumers.
6
+ * `pet.state` / `pet.pets` / `pet.interact` / `pet.setVisible` /
7
+ * `pet.setConfig` / `pet.setName` / `pet.setPet` for browser consumers.
8
8
  * @module @linxin666/dsh-pet/service
9
9
  */
10
10
 
@@ -21,6 +21,7 @@ import {
21
21
  } from './event-projection.ts'
22
22
  import { PetLedger, type LedgerConfig, type LedgerInteractionResult } from './ledger.ts'
23
23
  import {
24
+ DEFAULT_PET_NAME,
24
25
  DISPLAY_INSET_MAX,
25
26
  DISPLAY_SIZE_MAX,
26
27
  DISPLAY_SIZE_MIN,
@@ -31,6 +32,14 @@ import {
31
32
  type PetDisplayConfig,
32
33
  type PetPersist,
33
34
  } from './persist.ts'
35
+ import {
36
+ loadPetRegistry,
37
+ petEntryView,
38
+ petPackageRoot,
39
+ type PetDefinition,
40
+ type PetManifest,
41
+ type PetRegistry,
42
+ } from './registry.ts'
34
43
  import {
35
44
  defaultPetStateConfig,
36
45
  PetStateMachine,
@@ -51,14 +60,22 @@ export interface PetConfig {
51
60
  persistDir?: string
52
61
  /** Master switch for the plugin (browser half + host routes). */
53
62
  enabled?: boolean
63
+ /** Prebuilt registry (tests); defaults to scanning the package + user dirs. */
64
+ registry?: PetRegistry
65
+ /** Extra manifest entries composed by the embedding application. */
66
+ pets?: readonly PetManifest[]
54
67
  }
55
68
 
56
69
  /**
57
- * The pet's settings-namespace section: the display fields and name the web
58
- * settings surface edits. `right`/`bottom` are also updated by drag
70
+ * The pet's settings-namespace section: the pet selection and display fields
71
+ * the web settings surface edits. `right`/`bottom` are also updated by drag
59
72
  * interactions, which keep the settings document in sync through the service.
73
+ * Naming is per pet and lives outside the settings document (the hover-panel
74
+ * rename targets the selected pet).
60
75
  */
61
76
  export interface PetSettingsSection {
77
+ /** Selected pet id (a registry entry; the service clamps stale values). */
78
+ petId?: string
62
79
  /** Master switch. */
63
80
  visible: boolean
64
81
  /** Scale of the rendered pet in px (sprite cell height). */
@@ -67,8 +84,6 @@ export interface PetSettingsSection {
67
84
  right: number
68
85
  /** Vertical inset from the viewport bottom edge, px. */
69
86
  bottom: number
70
- /** User-customizable pet display name. */
71
- name: string
72
87
  /** Master switch for the plugin (browser half + host routes). */
73
88
  enabled?: boolean
74
89
  }
@@ -86,7 +101,16 @@ export interface PetStateView {
86
101
  affinity: PetAffinityView
87
102
  /** Display configuration. */
88
103
  display: PetDisplayConfig
89
- /** User-customizable pet display name. */
104
+ /** The selected pet's registry identity. */
105
+ pet: {
106
+ /** Registry id. */
107
+ id: string
108
+ /** Manifest display name (unrenamed default). */
109
+ displayName: string
110
+ /** Manifest description. */
111
+ description: string
112
+ }
113
+ /** The selected pet's display name (user rename or manifest default). */
90
114
  name: string
91
115
  /** Treat (小鱼干) stock snapshot. */
92
116
  treats: {
@@ -117,6 +141,7 @@ export class PetService extends Service {
117
141
 
118
142
  private readonly machine: PetStateMachine
119
143
  private readonly ledger: PetLedger
144
+ private readonly registry: PetRegistry
120
145
  private readonly persistDir: string
121
146
  private enabled: boolean
122
147
  private disposeActivity: (() => void) | undefined
@@ -127,8 +152,22 @@ export class PetService extends Service {
127
152
  constructor(ctx: Context, config: PetConfig = {}) {
128
153
  super(ctx, 'pet')
129
154
  this.persistDir = config.persistDir ?? petHomeDir()
155
+ this.registry = config.registry
156
+ ?? loadPetRegistry({
157
+ packageRoot: petPackageRoot(import.meta.url),
158
+ ...(config.pets === undefined ? {} : { extra: config.pets }),
159
+ })
160
+ if (this.registry.entries.length === 0) {
161
+ throw new Error('[dsh-pet] no valid pet manifests found; nothing to render')
162
+ }
163
+ let persist = loadPetPersist(this.persistDir)
164
+ if (this.registry.byId(persist.petId) === undefined) {
165
+ // The selected pet no longer exists (removed or a fresh install with a
166
+ // copied pet.json): fall back to the registry default.
167
+ persist = { ...persist, petId: this.registry.defaultEntry().id }
168
+ }
130
169
  const ledgerConfig: LedgerConfig = { affinity: config.affinity, treats: config.treats }
131
- this.ledger = new PetLedger(loadPetPersist(this.persistDir), ledgerConfig)
170
+ this.ledger = new PetLedger(persist, ledgerConfig)
132
171
  this.machine = new PetStateMachine({
133
172
  ...defaultPetStateConfig,
134
173
  ...(config.state ?? {}),
@@ -153,9 +192,41 @@ export class PetService extends Service {
153
192
  return { ...this.ledger.snapshot.display }
154
193
  }
155
194
 
156
- /** Current persisted pet name (read-only view). */
157
- petName(): string {
158
- return this.ledger.snapshot.name
195
+ /** RPC: the registry entries the browser half renders and selects from. */
196
+ async pets(): Promise<PetDefinition[]> {
197
+ return this.registry.entries.map(petEntryView)
198
+ }
199
+
200
+ /** The loaded registry (the asset routes serve its entries). */
201
+ registrySnapshot(): PetRegistry {
202
+ return this.registry
203
+ }
204
+
205
+ /** The selected pet's registry entry. */
206
+ activeEntry(): NonNullable<PetRegistry['entries'][number]> {
207
+ return this.registry.byId(this.selectedPetId()) ?? this.registry.defaultEntry()
208
+ }
209
+
210
+ /** Currently selected pet id (persisted). */
211
+ selectedPetId(): string {
212
+ return this.ledger.snapshot.petId
213
+ }
214
+
215
+ /** The display name of one pet (user rename or manifest displayName). */
216
+ petName(petId: string = this.selectedPetId()): string {
217
+ const stored = this.ledger.snapshot.names[petId]
218
+ if (stored !== undefined && stored.trim() !== '') return stored
219
+ return this.registry.byId(petId)?.displayName ?? DEFAULT_PET_NAME
220
+ }
221
+
222
+ /** RPC: switch the selected pet (persisted, settings document mirrored). */
223
+ async setPetId(petId: string): Promise<{ ok: true; petId: string } | { ok: false; error: string }> {
224
+ const entry = this.registry.byId(petId)
225
+ if (entry === undefined) return { ok: false, error: 'unknown-pet' }
226
+ this.ledger.setPetId(entry.id)
227
+ this.flush()
228
+ this.syncSettingsFromPet()
229
+ return { ok: true, petId: entry.id }
159
230
  }
160
231
 
161
232
  /** Start or stop the session-activity listeners that drive the pet. */
@@ -257,31 +328,36 @@ export class PetService extends Service {
257
328
  return { ok: true, display: this.ledger.snapshot.display }
258
329
  }
259
330
 
260
- /** RPC: rename the pet (trimmed, 1–20 chars). */
331
+ /** RPC: rename the selected pet (trimmed, 1–20 chars, per-pet storage). */
261
332
  async setName(name: string): Promise<{ ok: true; name: string } | { ok: false; error: string }> {
262
333
  const trimmed = name.trim()
263
334
  if (trimmed === '') return { ok: false, error: 'name-empty' }
264
335
  if (trimmed.length > PET_NAME_MAX_LENGTH) return { ok: false, error: 'name-too-long' }
265
- this.ledger.setName(trimmed)
336
+ this.ledger.setPetName(this.selectedPetId(), trimmed)
266
337
  this.flush()
267
- this.syncSettingsFromPet()
268
338
  return { ok: true, name: trimmed }
269
339
  }
270
340
 
271
341
  /**
272
- * Apply a committed settings section to the persisted display config. Called
273
- * by the settings surface on every change; values are clamped exactly like
274
- * the setConfig RPC so both write paths converge.
342
+ * Apply a committed settings section to the persisted selection and display
343
+ * config. Called by the settings surface on every change; values are
344
+ * clamped exactly like the setConfig RPC so both write paths converge.
275
345
  * @param section - the resolved settings section.
276
346
  */
277
347
  applySettingsSection(section: PetSettingsSection): void {
348
+ if (typeof section.petId === 'string' && this.registry.byId(section.petId) !== undefined) {
349
+ this.ledger.setPetId(section.petId)
350
+ } else if (section.petId !== undefined) {
351
+ // The stored selection names a pet the registry no longer has: keep the
352
+ // current selection and repair the settings document.
353
+ this.syncSettingsFromPet()
354
+ }
278
355
  const next = { ...this.ledger.snapshot.display }
279
356
  next.visible = section.visible && (section.enabled ?? true)
280
357
  next.size = Math.round(Math.min(DISPLAY_SIZE_MAX, Math.max(DISPLAY_SIZE_MIN, section.size)))
281
358
  next.right = Math.round(Math.min(DISPLAY_INSET_MAX, Math.max(0, section.right)))
282
359
  next.bottom = Math.round(Math.min(DISPLAY_INSET_MAX, Math.max(0, section.bottom)))
283
360
  this.ledger.setDisplay(next)
284
- this.ledger.setName(section.name.trim())
285
361
  this.flush()
286
362
  }
287
363
 
@@ -295,7 +371,7 @@ export class PetService extends Service {
295
371
  size: snapshot.display.size,
296
372
  right: snapshot.display.right,
297
373
  bottom: snapshot.display.bottom,
298
- name: snapshot.name,
374
+ petId: snapshot.petId,
299
375
  }).catch(() => {
300
376
  // A settings write failure must not break the pet's own persistence.
301
377
  })
@@ -313,6 +389,7 @@ export class PetService extends Service {
313
389
 
314
390
  private view(): PetStateView {
315
391
  const snapshot = this.machine.render()
392
+ const entry = this.activeEntry()
316
393
  // Read-only: the ledger settles on economic events only, never on a read,
317
394
  // so polling the state cannot trigger pet.json writes.
318
395
  return {
@@ -322,7 +399,12 @@ export class PetService extends Service {
322
399
  sessionActive: snapshot.sessionActive,
323
400
  affinity: this.ledger.affinityView(Date.now()),
324
401
  display: { ...this.ledger.snapshot.display },
325
- name: this.ledger.snapshot.name,
402
+ pet: {
403
+ id: entry.id,
404
+ displayName: entry.displayName,
405
+ description: entry.description,
406
+ },
407
+ name: this.petName(),
326
408
  treats: {
327
409
  stocked: this.ledger.snapshot.treats.treats,
328
410
  max: this.ledger.treatMax,
@@ -1 +0,0 @@
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"}