@linxin666/dsh-pet 0.4.3 → 0.4.4

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.
package/lib/client.js CHANGED
@@ -4132,7 +4132,7 @@ window.__ModuleLoader__.load({
4132
4132
  /** The building package's version, when the bundle carries it. */
4133
4133
  function bakedVersion() {
4134
4134
  try {
4135
- return "0.4.3";
4135
+ return "0.4.4";
4136
4136
  } catch {
4137
4137
  return;
4138
4138
  }
package/lib/index.js CHANGED
@@ -4638,6 +4638,15 @@ var PetService = class extends Service {
4638
4638
  * the same turn again after the pet is disabled and re-enabled.
4639
4639
  */
4640
4640
  officialEventSessions = /* @__PURE__ */ new WeakSet();
4641
+ /**
4642
+ * Resolve the settings namespace this plugin's own config is served as, when
4643
+ * the caller can read it off the composition. A plugin's settings ARE its own
4644
+ * config, so the namespace is the row's id — and an aggregate bundle renames
4645
+ * child rows (`pet` becomes `web-ui-pet`), which is why it cannot be assumed
4646
+ * to be {@link PET_SETTINGS_NAMESPACE}. `apply` sets this; absent means the
4647
+ * plugin runs outside a Loader and the package's own id stands.
4648
+ */
4649
+ settingsNamespaceProvider = void 0;
4641
4650
  constructor(ctx, config = {}) {
4642
4651
  super(ctx, "pet");
4643
4652
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -5194,8 +5203,9 @@ var PetService = class extends Service {
5194
5203
  syncSettingsFromPet() {
5195
5204
  const settings = this.ctx.get("settings", false);
5196
5205
  if (settings === void 0) return;
5206
+ const ns = this.settingsNamespaceProvider?.() ?? "pet";
5197
5207
  const snapshot = this.ledger.snapshot;
5198
- settings.update("pet", {
5208
+ settings.update(ns, {
5199
5209
  visible: snapshot.display.visible,
5200
5210
  size: snapshot.display.size,
5201
5211
  right: snapshot.display.right,
@@ -6171,27 +6181,91 @@ function readLive(field, fallback) {
6171
6181
  return typeof ref.get === "function" ? ref.get() ?? fallback : field;
6172
6182
  }
6173
6183
  /**
6184
+ * Resolve one display field of the section. The precedence is deliberate:
6185
+ *
6186
+ * 1. an explicit profile value (`committed`) — the user's own choice, and the
6187
+ * only signal that carries a value equal to the schema default on purpose;
6188
+ * 2. a live value the row committed at runtime, i.e. one that differs from the
6189
+ * schema default, which is all an untouched field ever resolves to;
6190
+ * 3. what the pet's own `pet.json` already holds (`persisted`), so a field the
6191
+ * profile never touched keeps the position the user dragged instead of
6192
+ * snapping back to the schema default on the next mount.
6193
+ *
6194
+ * Step 3 is what makes a drag survive a restart. The display fields carry
6195
+ * schema defaults (`right: 24`, `bottom: 20` — the bottom-right corner), and a
6196
+ * config that never set them reads back exactly like one that set them to the
6197
+ * defaults, so without `persisted` the startup section resets the layout every
6198
+ * time the profile commits none — which is what an aggregate install does.
6199
+ * @param key - the display field to resolve.
6200
+ * @param live - the row config's own field, absent when the entry declares none.
6201
+ * @param persisted - the display the pet already shows (its own `pet.json`).
6202
+ * @param committed - the profile layer's explicit display values, when known.
6203
+ * @returns the value the pet runs with.
6204
+ */
6205
+ function displayField(key, live, persisted, committed) {
6206
+ const explicit = committed[key];
6207
+ if (explicit !== void 0) return explicit;
6208
+ const value = readLive(live, PET_FORM_DEFAULTS[key]);
6209
+ if (value !== PET_FORM_DEFAULTS[key]) return value;
6210
+ const kept = persisted[key];
6211
+ return kept === void 0 ? value : kept;
6212
+ }
6213
+ /**
6174
6214
  * The settings section the pet runs with: the effective values of the row's
6175
6215
  * own config. `fallbackPetId` covers a mount whose config names no pet at all
6176
6216
  * (a direct mount outside a Loader) — under a Loader the schema default is
6177
6217
  * always present, so the persisted selection stands whenever the config
6178
- * carries it.
6218
+ * carries it. `persisted` and `committed` give the display fields the same
6219
+ * treatment; see {@link displayField}.
6179
6220
  * @param config - the effective config of the pet row.
6180
6221
  * @param fallbackPetId - pet id to use when the config names none.
6222
+ * @param persisted - the display the pet already shows (its own `pet.json`).
6223
+ * @param committed - the profile layer's explicit display values, when known.
6181
6224
  * @returns the resolved settings section.
6182
6225
  */
6183
- function petSettingsSection(config, fallbackPetId) {
6226
+ function petSettingsSection(config, fallbackPetId, persisted = {}, committed = {}) {
6184
6227
  return {
6185
- visible: readLive(config.visible, PET_FORM_DEFAULTS.visible),
6186
- size: readLive(config.size, PET_FORM_DEFAULTS.size),
6187
- right: readLive(config.right, PET_FORM_DEFAULTS.right),
6188
- bottom: readLive(config.bottom, PET_FORM_DEFAULTS.bottom),
6189
- bubbleScale: readLive(config.bubbleScale, PET_FORM_DEFAULTS.bubbleScale),
6228
+ visible: displayField("visible", config.visible, persisted, committed),
6229
+ size: displayField("size", config.size, persisted, committed),
6230
+ right: displayField("right", config.right, persisted, committed),
6231
+ bottom: displayField("bottom", config.bottom, persisted, committed),
6232
+ bubbleScale: displayField("bubbleScale", config.bubbleScale, persisted, committed),
6190
6233
  petId: readLive(config.petId, fallbackPetId),
6191
6234
  enabled: readLive(config.enabled, PET_FORM_DEFAULTS.enabled),
6192
6235
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled)
6193
6236
  };
6194
6237
  }
6238
+ /** The settings row ids a pet install is served under, most specific first. */
6239
+ const PET_SETTINGS_ROW_IDS = [
6240
+ "web-ui-pet",
6241
+ "ui-pet",
6242
+ "pet"
6243
+ ];
6244
+ /**
6245
+ * The settings row serving this plugin's own config, or undefined when the
6246
+ * settings service is not mounted (a direct mount) or serves no row for the pet.
6247
+ *
6248
+ * Every settings write has to address this row's id, and that id is not the
6249
+ * package's plugin name: an aggregate bundle renames child rows (`pet` becomes
6250
+ * `web-ui-pet`). The browser half resolves the same id through its own
6251
+ * `servedEntryId`, so both halves write to one namespace.
6252
+ * @param ctx - the plugin's context.
6253
+ * @returns the serving row, when there is one.
6254
+ */
6255
+ function servedRow(ctx) {
6256
+ try {
6257
+ const settings = ctx.get("settings", false);
6258
+ if (settings === void 0) return void 0;
6259
+ const rows = settings.describe();
6260
+ for (const id of PET_SETTINGS_ROW_IDS) {
6261
+ const row = rows.find((candidate) => candidate.ns === id);
6262
+ if (row !== void 0) return row;
6263
+ }
6264
+ return;
6265
+ } catch {
6266
+ return;
6267
+ }
6268
+ }
6195
6269
  /** Register the pet service and its API + asset routes on the context. */
6196
6270
  const apply = mountOnce("@linxin666/dsh-pet", applyImpl);
6197
6271
  function applyImpl(ctx, config = {}) {
@@ -6205,7 +6279,8 @@ function applyImpl(ctx, config = {}) {
6205
6279
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
6206
6280
  registry
6207
6281
  });
6208
- const current = () => petSettingsSection(config, service.selectedPetId());
6282
+ service.settingsNamespaceProvider = () => servedRow(ctx)?.ns;
6283
+ const current = () => petSettingsSection(config, service.selectedPetId(), service.display(), servedRow(ctx)?.user);
6209
6284
  const routes = makePetRoutes({
6210
6285
  service,
6211
6286
  ctx
@@ -13,6 +13,7 @@
13
13
  import { Context, type Volatile } from '@deepseek-ai/cordis';
14
14
  import z from '@deepseek-ai/schemastery';
15
15
  import { type PetConfig, type PetSettingsSection } from './service.ts';
16
+ import { type PetDisplayConfig } from './persist.ts';
16
17
  export { PetService, MAX_SESSION_BUBBLES } from './service.ts';
17
18
  export type { PetConfig, PetInteractResult, PetSettingsSection, PetSessionView, PetStateView, } from './service.ts';
18
19
  export { AFFINITY_MAX, AFFINITY_RANKS, applyInteraction, applyTurnReward, emptyAffinity, rankOf, } from './affinity.ts';
@@ -112,12 +113,15 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
112
113
  * own config. `fallbackPetId` covers a mount whose config names no pet at all
113
114
  * (a direct mount outside a Loader) — under a Loader the schema default is
114
115
  * always present, so the persisted selection stands whenever the config
115
- * carries it.
116
+ * carries it. `persisted` and `committed` give the display fields the same
117
+ * treatment; see {@link displayField}.
116
118
  * @param config - the effective config of the pet row.
117
119
  * @param fallbackPetId - pet id to use when the config names none.
120
+ * @param persisted - the display the pet already shows (its own `pet.json`).
121
+ * @param committed - the profile layer's explicit display values, when known.
118
122
  * @returns the resolved settings section.
119
123
  */
120
- export declare function petSettingsSection(config: PetFormConfig, fallbackPetId: string): PetSettingsSection;
124
+ export declare function petSettingsSection(config: PetFormConfig, fallbackPetId: string, persisted?: Partial<PetDisplayConfig>, committed?: Partial<PetDisplayConfig>): PetSettingsSection;
121
125
  /** Register the pet service and its API + asset routes on the context. */
122
126
  export declare const apply: typeof applyImpl;
123
127
  /** Plugin config: the tuning block a profile may declare plus the pet's settings fields. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@
11
11
  * @module @linxin666/dsh-pet
12
12
  */
13
13
  import z from '@deepseek-ai/schemastery';
14
- import { PetService } from "./service.js";
14
+ import { PET_SETTINGS_NAMESPACE, PetService } from "./service.js";
15
15
  import { makePetRoutes } from "./routes.js";
16
16
  import { loadPetRegistry, petPackageRoot } from "./registry.js";
17
17
  import { BUBBLE_SCALE_MAX, BUBBLE_SCALE_MIN, DEFAULT_PET_ID, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN } from "./persist.js";
@@ -82,28 +82,93 @@ function readLive(field, fallback) {
82
82
  const ref = field;
83
83
  return typeof ref.get === 'function' ? ref.get() ?? fallback : field;
84
84
  }
85
+ /**
86
+ * Resolve one display field of the section. The precedence is deliberate:
87
+ *
88
+ * 1. an explicit profile value (`committed`) — the user's own choice, and the
89
+ * only signal that carries a value equal to the schema default on purpose;
90
+ * 2. a live value the row committed at runtime, i.e. one that differs from the
91
+ * schema default, which is all an untouched field ever resolves to;
92
+ * 3. what the pet's own `pet.json` already holds (`persisted`), so a field the
93
+ * profile never touched keeps the position the user dragged instead of
94
+ * snapping back to the schema default on the next mount.
95
+ *
96
+ * Step 3 is what makes a drag survive a restart. The display fields carry
97
+ * schema defaults (`right: 24`, `bottom: 20` — the bottom-right corner), and a
98
+ * config that never set them reads back exactly like one that set them to the
99
+ * defaults, so without `persisted` the startup section resets the layout every
100
+ * time the profile commits none — which is what an aggregate install does.
101
+ * @param key - the display field to resolve.
102
+ * @param live - the row config's own field, absent when the entry declares none.
103
+ * @param persisted - the display the pet already shows (its own `pet.json`).
104
+ * @param committed - the profile layer's explicit display values, when known.
105
+ * @returns the value the pet runs with.
106
+ */
107
+ function displayField(key, live, persisted, committed) {
108
+ const explicit = committed[key];
109
+ if (explicit !== undefined)
110
+ return explicit;
111
+ const value = readLive(live, PET_FORM_DEFAULTS[key]);
112
+ if (value !== PET_FORM_DEFAULTS[key])
113
+ return value;
114
+ const kept = persisted[key];
115
+ return kept === undefined ? value : kept;
116
+ }
85
117
  /**
86
118
  * The settings section the pet runs with: the effective values of the row's
87
119
  * own config. `fallbackPetId` covers a mount whose config names no pet at all
88
120
  * (a direct mount outside a Loader) — under a Loader the schema default is
89
121
  * always present, so the persisted selection stands whenever the config
90
- * carries it.
122
+ * carries it. `persisted` and `committed` give the display fields the same
123
+ * treatment; see {@link displayField}.
91
124
  * @param config - the effective config of the pet row.
92
125
  * @param fallbackPetId - pet id to use when the config names none.
126
+ * @param persisted - the display the pet already shows (its own `pet.json`).
127
+ * @param committed - the profile layer's explicit display values, when known.
93
128
  * @returns the resolved settings section.
94
129
  */
95
- export function petSettingsSection(config, fallbackPetId) {
130
+ export function petSettingsSection(config, fallbackPetId, persisted = {}, committed = {}) {
96
131
  return {
97
- visible: readLive(config.visible, PET_FORM_DEFAULTS.visible),
98
- size: readLive(config.size, PET_FORM_DEFAULTS.size),
99
- right: readLive(config.right, PET_FORM_DEFAULTS.right),
100
- bottom: readLive(config.bottom, PET_FORM_DEFAULTS.bottom),
101
- bubbleScale: readLive(config.bubbleScale, PET_FORM_DEFAULTS.bubbleScale),
132
+ visible: displayField('visible', config.visible, persisted, committed),
133
+ size: displayField('size', config.size, persisted, committed),
134
+ right: displayField('right', config.right, persisted, committed),
135
+ bottom: displayField('bottom', config.bottom, persisted, committed),
136
+ bubbleScale: displayField('bubbleScale', config.bubbleScale, persisted, committed),
102
137
  petId: readLive(config.petId, fallbackPetId),
103
138
  enabled: readLive(config.enabled, PET_FORM_DEFAULTS.enabled),
104
139
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
105
140
  };
106
141
  }
142
+ /** The settings row ids a pet install is served under, most specific first. */
143
+ const PET_SETTINGS_ROW_IDS = ['web-ui-pet', 'ui-pet', PET_SETTINGS_NAMESPACE];
144
+ /**
145
+ * The settings row serving this plugin's own config, or undefined when the
146
+ * settings service is not mounted (a direct mount) or serves no row for the pet.
147
+ *
148
+ * Every settings write has to address this row's id, and that id is not the
149
+ * package's plugin name: an aggregate bundle renames child rows (`pet` becomes
150
+ * `web-ui-pet`). The browser half resolves the same id through its own
151
+ * `servedEntryId`, so both halves write to one namespace.
152
+ * @param ctx - the plugin's context.
153
+ * @returns the serving row, when there is one.
154
+ */
155
+ function servedRow(ctx) {
156
+ try {
157
+ const settings = ctx.get('settings', false);
158
+ if (settings === undefined)
159
+ return undefined;
160
+ const rows = settings.describe();
161
+ for (const id of PET_SETTINGS_ROW_IDS) {
162
+ const row = rows.find(candidate => candidate.ns === id);
163
+ if (row !== undefined)
164
+ return row;
165
+ }
166
+ return undefined;
167
+ }
168
+ catch {
169
+ return undefined;
170
+ }
171
+ }
107
172
  /** Register the pet service and its API + asset routes on the context. */
108
173
  export const apply = mountOnce('@linxin666/dsh-pet', applyImpl);
109
174
  function applyImpl(ctx, config = {}) {
@@ -118,12 +183,19 @@ function applyImpl(ctx, config = {}) {
118
183
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
119
184
  registry,
120
185
  });
186
+ // The mirror back into the settings document (see syncSettingsFromPet) has to
187
+ // address the row that serves this plugin's config, which an aggregate bundle
188
+ // renames; the id is resolved lazily, once the composition serves it.
189
+ service.settingsNamespaceProvider = () => servedRow(ctx)?.ns;
121
190
  // The effective settings ARE this row's own config: the Host serves one form
122
191
  // per profile entry from `Config` above, and every edit is committed into the
123
192
  // running config, so the plugin re-reads the section here instead of holding
124
193
  // a separate settings document (0.1.6 registered one through
125
- // settings.installSection/register and re-resolved it on every change).
126
- const current = () => petSettingsSection(config, service.selectedPetId());
194
+ // settings.installSection/register and re-resolved it on every change). The
195
+ // display fields additionally read the persisted layout and the profile layer,
196
+ // so a mount with a config that never set them cannot reset a dragged
197
+ // position to the schema defaults (see displayField).
198
+ const current = () => petSettingsSection(config, service.selectedPetId(), service.display(), servedRow(ctx)?.user);
127
199
  // The browser half talks to the pet through same-origin JSON endpoints and
128
200
  // loads each pet's atlas from the registry's own media route (RPC domains
129
201
  // are platform-registered, so the pet serves its own API — the same
@@ -222,6 +222,15 @@ export declare class PetService extends Service {
222
222
  * the same turn again after the pet is disabled and re-enabled.
223
223
  */
224
224
  private readonly officialEventSessions;
225
+ /**
226
+ * Resolve the settings namespace this plugin's own config is served as, when
227
+ * the caller can read it off the composition. A plugin's settings ARE its own
228
+ * config, so the namespace is the row's id — and an aggregate bundle renames
229
+ * child rows (`pet` becomes `web-ui-pet`), which is why it cannot be assumed
230
+ * to be {@link PET_SETTINGS_NAMESPACE}. `apply` sets this; absent means the
231
+ * plugin runs outside a Loader and the package's own id stands.
232
+ */
233
+ settingsNamespaceProvider: (() => string | undefined) | undefined;
225
234
  constructor(ctx: Context, config?: PetConfig);
226
235
  /**
227
236
  * The draw-time voice-pool provider handed to every projection runtime.
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,15 @@ export class PetService extends Service {
66
66
  * the same turn again after the pet is disabled and re-enabled.
67
67
  */
68
68
  officialEventSessions = new WeakSet();
69
+ /**
70
+ * Resolve the settings namespace this plugin's own config is served as, when
71
+ * the caller can read it off the composition. A plugin's settings ARE its own
72
+ * config, so the namespace is the row's id — and an aggregate bundle renames
73
+ * child rows (`pet` becomes `web-ui-pet`), which is why it cannot be assumed
74
+ * to be {@link PET_SETTINGS_NAMESPACE}. `apply` sets this; absent means the
75
+ * plugin runs outside a Loader and the package's own id stands.
76
+ */
77
+ settingsNamespaceProvider = undefined;
69
78
  constructor(ctx, config = {}) {
70
79
  super(ctx, 'pet');
71
80
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -609,8 +618,9 @@ export class PetService extends Service {
609
618
  const settings = this.ctx.get('settings', false);
610
619
  if (settings === undefined)
611
620
  return;
621
+ const ns = this.settingsNamespaceProvider?.() ?? PET_SETTINGS_NAMESPACE;
612
622
  const snapshot = this.ledger.snapshot;
613
- void settings.update(PET_SETTINGS_NAMESPACE, {
623
+ void settings.update(ns, {
614
624
  visible: snapshot.display.visible,
615
625
  size: snapshot.display.size,
616
626
  right: snapshot.display.right,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-pet",
3
3
  "description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
package/src/index.test.ts CHANGED
@@ -117,3 +117,42 @@ describe('petSettingsSection', () => {
117
117
  expect(section.petId).toBe('doro')
118
118
  })
119
119
  })
120
+
121
+ describe('petSettingsSection display persistence', () => {
122
+ /** The layout a user dragged the pet to, as its own pet.json holds it. */
123
+ const dragged = { visible: true, size: 160, right: 1428, bottom: 167, bubbleScale: 1 }
124
+
125
+ it('user keeps the dragged position when the profile never committed one', () => {
126
+ // Given a pet.json holding the layout the pet was dragged to
127
+ // And a row config that never set the display fields, so they read back as
128
+ // the schema defaults (the bottom-right corner)
129
+ // When the plugin starts and resolves its active settings
130
+ const section = petSettingsSection(Config({}), 'ouo-neko', dragged)
131
+ // Then the dragged layout stands instead of snapping back to those defaults
132
+ expect(section).toMatchObject({ right: 1428, bottom: 167 })
133
+ })
134
+
135
+ it('user commits a display field and the profile value outranks the persisted one', () => {
136
+ // Given a dragged layout and a profile layer that committed a right inset
137
+ const section = petSettingsSection(Config({}), 'ouo-neko', dragged, { right: 48 })
138
+ // Then the explicit choice wins and the untouched field keeps the dragged one
139
+ expect(section).toMatchObject({ right: 48, bottom: 167 })
140
+ })
141
+
142
+ it('user commits the schema default and the choice is not mistaken for unset', () => {
143
+ // Given a dragged layout and a profile layer that committed the default inset
144
+ const section = petSettingsSection(Config({}), 'ouo-neko', dragged, { right: 24 })
145
+ // Then the explicit (default-valued) choice stands over the persisted one
146
+ expect(section.right).toBe(24)
147
+ })
148
+
149
+ it('user commits a layout at runtime and the result outranks the persisted one', () => {
150
+ // Given a live reference the Host committed into the running config
151
+ const resolved = Config({})
152
+ commitLive(resolved.right, 500)
153
+ // When the plugin resolves the section against an older persisted layout
154
+ const section = petSettingsSection(resolved, 'ouo-neko', dragged)
155
+ // Then the live commit wins and the persisted value fills the other fields
156
+ expect(section).toMatchObject({ right: 500, bottom: 167 })
157
+ })
158
+ })
package/src/index.ts CHANGED
@@ -16,10 +16,10 @@ import { Context, type Volatile } from '@deepseek-ai/cordis'
16
16
  import type {} from '@deepseek-ai/dsh-settings'
17
17
  import type {} from '@deepseek-ai/dsh-host-webserver'
18
18
  import z from '@deepseek-ai/schemastery'
19
- import { PetService, type PetConfig, type PetSettingsSection } from './service.ts'
19
+ import { PET_SETTINGS_NAMESPACE, PetService, type PetConfig, type PetSettingsSection } from './service.ts'
20
20
  import { makePetRoutes } from './routes.ts'
21
21
  import { loadPetRegistry, petPackageRoot } from './registry.ts'
22
- import { BUBBLE_SCALE_MAX, BUBBLE_SCALE_MIN, DEFAULT_PET_ID, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN } from './persist.ts'
22
+ import { BUBBLE_SCALE_MAX, BUBBLE_SCALE_MIN, DEFAULT_PET_ID, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN, type PetDisplayConfig } from './persist.ts'
23
23
  import { mountOnce } from './mount-once.ts'
24
24
 
25
25
  export { PetService, MAX_SESSION_BUBBLES } from './service.ts'
@@ -203,29 +203,115 @@ function readLive<T>(field: LiveField<T> | undefined, fallback: T): T {
203
203
  return typeof ref.get === 'function' ? ref.get() ?? fallback : field as T
204
204
  }
205
205
 
206
+ /**
207
+ * Resolve one display field of the section. The precedence is deliberate:
208
+ *
209
+ * 1. an explicit profile value (`committed`) — the user's own choice, and the
210
+ * only signal that carries a value equal to the schema default on purpose;
211
+ * 2. a live value the row committed at runtime, i.e. one that differs from the
212
+ * schema default, which is all an untouched field ever resolves to;
213
+ * 3. what the pet's own `pet.json` already holds (`persisted`), so a field the
214
+ * profile never touched keeps the position the user dragged instead of
215
+ * snapping back to the schema default on the next mount.
216
+ *
217
+ * Step 3 is what makes a drag survive a restart. The display fields carry
218
+ * schema defaults (`right: 24`, `bottom: 20` — the bottom-right corner), and a
219
+ * config that never set them reads back exactly like one that set them to the
220
+ * defaults, so without `persisted` the startup section resets the layout every
221
+ * time the profile commits none — which is what an aggregate install does.
222
+ * @param key - the display field to resolve.
223
+ * @param live - the row config's own field, absent when the entry declares none.
224
+ * @param persisted - the display the pet already shows (its own `pet.json`).
225
+ * @param committed - the profile layer's explicit display values, when known.
226
+ * @returns the value the pet runs with.
227
+ */
228
+ function displayField<K extends keyof PetDisplayConfig>(
229
+ key: K,
230
+ live: LiveField<PetDisplayConfig[K]> | undefined,
231
+ persisted: Partial<PetDisplayConfig>,
232
+ committed: Partial<PetDisplayConfig>,
233
+ ): PetDisplayConfig[K] {
234
+ const explicit = committed[key]
235
+ if (explicit !== undefined) return explicit
236
+ const value = readLive<PetDisplayConfig[K]>(live, PET_FORM_DEFAULTS[key])
237
+ if (value !== PET_FORM_DEFAULTS[key]) return value
238
+ const kept = persisted[key]
239
+ return kept === undefined ? value : kept
240
+ }
241
+
206
242
  /**
207
243
  * The settings section the pet runs with: the effective values of the row's
208
244
  * own config. `fallbackPetId` covers a mount whose config names no pet at all
209
245
  * (a direct mount outside a Loader) — under a Loader the schema default is
210
246
  * always present, so the persisted selection stands whenever the config
211
- * carries it.
247
+ * carries it. `persisted` and `committed` give the display fields the same
248
+ * treatment; see {@link displayField}.
212
249
  * @param config - the effective config of the pet row.
213
250
  * @param fallbackPetId - pet id to use when the config names none.
251
+ * @param persisted - the display the pet already shows (its own `pet.json`).
252
+ * @param committed - the profile layer's explicit display values, when known.
214
253
  * @returns the resolved settings section.
215
254
  */
216
- export function petSettingsSection(config: PetFormConfig, fallbackPetId: string): PetSettingsSection {
255
+ export function petSettingsSection(
256
+ config: PetFormConfig,
257
+ fallbackPetId: string,
258
+ persisted: Partial<PetDisplayConfig> = {},
259
+ committed: Partial<PetDisplayConfig> = {},
260
+ ): PetSettingsSection {
217
261
  return {
218
- visible: readLive(config.visible, PET_FORM_DEFAULTS.visible),
219
- size: readLive(config.size, PET_FORM_DEFAULTS.size),
220
- right: readLive(config.right, PET_FORM_DEFAULTS.right),
221
- bottom: readLive(config.bottom, PET_FORM_DEFAULTS.bottom),
222
- bubbleScale: readLive(config.bubbleScale, PET_FORM_DEFAULTS.bubbleScale),
262
+ visible: displayField('visible', config.visible, persisted, committed),
263
+ size: displayField('size', config.size, persisted, committed),
264
+ right: displayField('right', config.right, persisted, committed),
265
+ bottom: displayField('bottom', config.bottom, persisted, committed),
266
+ bubbleScale: displayField('bubbleScale', config.bubbleScale, persisted, committed),
223
267
  petId: readLive(config.petId, fallbackPetId),
224
268
  enabled: readLive(config.enabled, PET_FORM_DEFAULTS.enabled),
225
269
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
226
270
  }
227
271
  }
228
272
 
273
+ /** The settings row ids a pet install is served under, most specific first. */
274
+ const PET_SETTINGS_ROW_IDS: readonly string[] = ['web-ui-pet', 'ui-pet', PET_SETTINGS_NAMESPACE]
275
+
276
+ /**
277
+ * The fields this host half reads off one settings descriptor. The settings
278
+ * service serves one descriptor per plugin row (see `describe`), and a plugin's
279
+ * own config IS its settings page, so the two that matter here are the row's
280
+ * namespace and the profile layer's explicit values.
281
+ */
282
+ interface ServedSettingsRow {
283
+ /** The profile entry's id, which is also this plugin's settings namespace. */
284
+ ns: string
285
+ /** The profile layer's explicit values; absent fields were never committed. */
286
+ user?: Partial<PetDisplayConfig>
287
+ }
288
+
289
+ /**
290
+ * The settings row serving this plugin's own config, or undefined when the
291
+ * settings service is not mounted (a direct mount) or serves no row for the pet.
292
+ *
293
+ * Every settings write has to address this row's id, and that id is not the
294
+ * package's plugin name: an aggregate bundle renames child rows (`pet` becomes
295
+ * `web-ui-pet`). The browser half resolves the same id through its own
296
+ * `servedEntryId`, so both halves write to one namespace.
297
+ * @param ctx - the plugin's context.
298
+ * @returns the serving row, when there is one.
299
+ */
300
+ function servedRow(ctx: Context): ServedSettingsRow | undefined {
301
+ try {
302
+ const settings = ctx.get('settings', false) as { describe(): readonly ServedSettingsRow[] } | undefined
303
+ if (settings === undefined) return undefined
304
+ const rows = settings.describe()
305
+ for (const id of PET_SETTINGS_ROW_IDS) {
306
+ const row = rows.find(candidate => candidate.ns === id)
307
+ if (row !== undefined) return row
308
+ }
309
+ return undefined
310
+ } catch {
311
+ return undefined
312
+ }
313
+ }
314
+
229
315
  /** Register the pet service and its API + asset routes on the context. */
230
316
  export const apply = mountOnce('@linxin666/dsh-pet', applyImpl)
231
317
 
@@ -244,13 +330,21 @@ function applyImpl(ctx: Context, config: PetPluginConfig = {}): void {
244
330
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
245
331
  registry,
246
332
  })
333
+ // The mirror back into the settings document (see syncSettingsFromPet) has to
334
+ // address the row that serves this plugin's config, which an aggregate bundle
335
+ // renames; the id is resolved lazily, once the composition serves it.
336
+ service.settingsNamespaceProvider = () => servedRow(ctx)?.ns
247
337
 
248
338
  // The effective settings ARE this row's own config: the Host serves one form
249
339
  // per profile entry from `Config` above, and every edit is committed into the
250
340
  // running config, so the plugin re-reads the section here instead of holding
251
341
  // a separate settings document (0.1.6 registered one through
252
- // settings.installSection/register and re-resolved it on every change).
253
- const current = (): PetSettingsSection => petSettingsSection(config, service.selectedPetId())
342
+ // settings.installSection/register and re-resolved it on every change). The
343
+ // display fields additionally read the persisted layout and the profile layer,
344
+ // so a mount with a config that never set them cannot reset a dragged
345
+ // position to the schema defaults (see displayField).
346
+ const current = (): PetSettingsSection =>
347
+ petSettingsSection(config, service.selectedPetId(), service.display(), servedRow(ctx)?.user)
254
348
  // The browser half talks to the pet through same-origin JSON endpoints and
255
349
  // loads each pet's atlas from the registry's own media route (RPC domains
256
350
  // are platform-registered, so the pet serves its own API — the same
package/src/service.ts CHANGED
@@ -300,6 +300,15 @@ export class PetService extends Service {
300
300
  * the same turn again after the pet is disabled and re-enabled.
301
301
  */
302
302
  private readonly officialEventSessions = new WeakSet<Session>()
303
+ /**
304
+ * Resolve the settings namespace this plugin's own config is served as, when
305
+ * the caller can read it off the composition. A plugin's settings ARE its own
306
+ * config, so the namespace is the row's id — and an aggregate bundle renames
307
+ * child rows (`pet` becomes `web-ui-pet`), which is why it cannot be assumed
308
+ * to be {@link PET_SETTINGS_NAMESPACE}. `apply` sets this; absent means the
309
+ * plugin runs outside a Loader and the package's own id stands.
310
+ */
311
+ settingsNamespaceProvider: (() => string | undefined) | undefined = undefined
303
312
 
304
313
  constructor(ctx: Context, config: PetConfig = {}) {
305
314
  super(ctx, 'pet')
@@ -836,8 +845,9 @@ export class PetService extends Service {
836
845
  private syncSettingsFromPet(): void {
837
846
  const settings = this.ctx.get('settings', false) as { update(ns: string, patch: object): Promise<void> } | undefined
838
847
  if (settings === undefined) return
848
+ const ns = this.settingsNamespaceProvider?.() ?? PET_SETTINGS_NAMESPACE
839
849
  const snapshot = this.ledger.snapshot
840
- void settings.update(PET_SETTINGS_NAMESPACE, {
850
+ void settings.update(ns, {
841
851
  visible: snapshot.display.visible,
842
852
  size: snapshot.display.size,
843
853
  right: snapshot.display.right,