@linxin666/dsh-pet 0.4.2 → 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/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,
@@ -6007,38 +6017,111 @@ function makePetRoutes(deps) {
6007
6017
  * the loader accepts a standalone install of the same package side by side;
6008
6018
  * without this guard the second instance would still re-register the same
6009
6019
  * webserver routes, tools, settings namespaces, and system-prompt sections
6010
- * and fail the boot. mountOnce makes the second host apply a no-op for the
6011
- * lifetime of the first instance (the browser half is already deduped by
6012
- * package name in the client module host).
6020
+ * and fail the boot. mountOnce makes a mount of an already-mounted package a
6021
+ * no-op for as long as the first instance lives (the browser half is already
6022
+ * deduped by package name in the client module host).
6023
+ *
6024
+ * A no-op is only safe while the holder is ALIVE. The holder can be disposed
6025
+ * long after the refused mount ran its course: the Host reloads a profile by
6026
+ * creating the new loader entries before the old ones are torn down (a
6027
+ * plugin-manager enable/disable/install write, a settings-driven row reload,
6028
+ * HMR), so the new aggregate shell entry mounts its family plugin while the
6029
+ * previous entry still owns the name. Dropping that refused mount lost the
6030
+ * plugin for good - the previous entry then disposed its own mount, releasing
6031
+ * the name with nobody left to take it, and the family row stayed listed as
6032
+ * active while its host routes 404ed (the task-board panel showed
6033
+ * "board.hostError.notMounted", no degraded record appeared, and only a Host
6034
+ * restart recovered it).
6035
+ *
6036
+ * The refused mount is therefore QUEUED, not dropped, and replayed the moment
6037
+ * the holder releases the name - if the waiting fiber is still alive then. The
6038
+ * single-instance guarantee is unchanged: exactly one mount is live per
6039
+ * package name, and the replay re-enters the guard so a later mount still
6040
+ * dedupes against it.
6013
6041
  *
6014
6042
  * The registry rides a global symbol so two module instances of the same
6015
- * package (npm copy vs repository link) still share one verdict. cordis
6016
- * `ctx.effect` runs its callback immediately and treats the callback's
6043
+ * package (npm copy vs repository link) still share one verdict. That symbol
6044
+ * is a CROSS-REPOSITORY contract, not this file's private state: the four
6045
+ * satellite packages (dsh-skins / dsh-pet / dsh-presets /
6046
+ * dsh-community-plugins) are separate repositories carrying their own copy of
6047
+ * this guard, rebuilt on their own schedule, so the value under `MOUNTED`
6048
+ * must keep the shape every published copy reads (a `Set` of package names
6049
+ * with `has`/`add`/`delete`). The wait queues this guard added therefore live
6050
+ * under their own additive key, and the registry reads back a `Set` even when
6051
+ * some other build left a different value there. Changing `MOUNTED`'s shape
6052
+ * in place broke that contract once: a satellite's legacy copy created a
6053
+ * `Set`, the family's new copy read it as a `Map`, and every family row
6054
+ * mounted after it failed with "claims.get is not a function".
6055
+ *
6056
+ * cordis `ctx.effect` runs its callback immediately and treats the callback's
6017
6057
  * return value as the fiber disposer, so the unmarker is returned, not run.
6018
6058
  */
6059
+ /** Published cross-repository contract: package names currently mounted. */
6019
6060
  const MOUNTED = Symbol.for("dsh-web.mounted-plugins");
6061
+ /** Additive key this guard owns: refused mounts waiting for the name. */
6062
+ const WAITERS = Symbol.for("dsh-web.mounted-plugins.waiters");
6063
+ /**
6064
+ * The shared name registry, always a `Set` whatever another build stored here:
6065
+ * a foreign value (an interim shape, a hand-written global) must not take every
6066
+ * family plugin down with it.
6067
+ * @returns the process-wide set of mounted package names.
6068
+ */
6020
6069
  function mountedSet() {
6021
6070
  const registry = globalThis;
6022
- return registry[MOUNTED] ??= /* @__PURE__ */ new Set();
6071
+ const existing = registry[MOUNTED];
6072
+ if (existing instanceof Set) return existing;
6073
+ const created = /* @__PURE__ */ new Set();
6074
+ registry[MOUNTED] = created;
6075
+ return created;
6076
+ }
6077
+ /** Queue per package name for mounts refused while a holder was alive. */
6078
+ function mountWaiters() {
6079
+ const registry = globalThis;
6080
+ return registry[WAITERS] ??= /* @__PURE__ */ new Map();
6023
6081
  }
6024
6082
  /**
6025
6083
  * Wrap a cordis plugin apply so the package runs at most once per process.
6026
- * The first mount registers normally and unmarks when its fiber disposes;
6027
- * any later mount of the same package name is a no-op.
6084
+ * The first mount registers normally and releases the name when its fiber
6085
+ * disposes; a mount refused while that name is held waits for the release and
6086
+ * then runs, unless its own fiber disposes first.
6028
6087
  * @param packageName - npm package identity shared by every install source.
6029
6088
  * @param fn - the original plugin apply.
6030
6089
  * @returns an apply of the same shape.
6031
6090
  */
6032
6091
  function mountOnce(packageName, fn) {
6033
- return ((...args) => {
6092
+ const mount = (...args) => {
6034
6093
  const mounted = mountedSet();
6035
- if (mounted.has(packageName)) return;
6094
+ const ctx = args[0];
6095
+ if (mounted.has(packageName)) {
6096
+ const waiters = mountWaiters();
6097
+ const queue = waiters.get(packageName) ?? [];
6098
+ let alive = true;
6099
+ const pending = { run: () => {
6100
+ if (alive) mount(...args);
6101
+ } };
6102
+ ctx?.effect?.(() => () => {
6103
+ alive = false;
6104
+ const index = queue.indexOf(pending);
6105
+ if (index >= 0) queue.splice(index, 1);
6106
+ });
6107
+ queue.push(pending);
6108
+ waiters.set(packageName, queue);
6109
+ return;
6110
+ }
6036
6111
  mounted.add(packageName);
6037
- args[0]?.effect?.(() => () => {
6112
+ ctx?.effect?.(() => () => {
6038
6113
  mounted.delete(packageName);
6114
+ const waiters = mountWaiters();
6115
+ const queue = waiters.get(packageName);
6116
+ if (queue === void 0) return;
6117
+ waiters.delete(packageName);
6118
+ for (const waiter of queue.splice(0)) queueMicrotask(() => {
6119
+ waiter.run();
6120
+ });
6039
6121
  });
6040
6122
  return fn(...args);
6041
- });
6123
+ };
6124
+ return mount;
6042
6125
  }
6043
6126
  //#endregion
6044
6127
  //#region src/index.ts
@@ -6098,27 +6181,91 @@ function readLive(field, fallback) {
6098
6181
  return typeof ref.get === "function" ? ref.get() ?? fallback : field;
6099
6182
  }
6100
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
+ /**
6101
6214
  * The settings section the pet runs with: the effective values of the row's
6102
6215
  * own config. `fallbackPetId` covers a mount whose config names no pet at all
6103
6216
  * (a direct mount outside a Loader) — under a Loader the schema default is
6104
6217
  * always present, so the persisted selection stands whenever the config
6105
- * carries it.
6218
+ * carries it. `persisted` and `committed` give the display fields the same
6219
+ * treatment; see {@link displayField}.
6106
6220
  * @param config - the effective config of the pet row.
6107
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.
6108
6224
  * @returns the resolved settings section.
6109
6225
  */
6110
- function petSettingsSection(config, fallbackPetId) {
6226
+ function petSettingsSection(config, fallbackPetId, persisted = {}, committed = {}) {
6111
6227
  return {
6112
- visible: readLive(config.visible, PET_FORM_DEFAULTS.visible),
6113
- size: readLive(config.size, PET_FORM_DEFAULTS.size),
6114
- right: readLive(config.right, PET_FORM_DEFAULTS.right),
6115
- bottom: readLive(config.bottom, PET_FORM_DEFAULTS.bottom),
6116
- 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),
6117
6233
  petId: readLive(config.petId, fallbackPetId),
6118
6234
  enabled: readLive(config.enabled, PET_FORM_DEFAULTS.enabled),
6119
6235
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled)
6120
6236
  };
6121
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
+ }
6122
6269
  /** Register the pet service and its API + asset routes on the context. */
6123
6270
  const apply = mountOnce("@linxin666/dsh-pet", applyImpl);
6124
6271
  function applyImpl(ctx, config = {}) {
@@ -6132,7 +6279,8 @@ function applyImpl(ctx, config = {}) {
6132
6279
  decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
6133
6280
  registry
6134
6281
  });
6135
- const current = () => petSettingsSection(config, service.selectedPetId());
6282
+ service.settingsNamespaceProvider = () => servedRow(ctx)?.ns;
6283
+ const current = () => petSettingsSection(config, service.selectedPetId(), service.display(), servedRow(ctx)?.user);
6136
6284
  const routes = makePetRoutes({
6137
6285
  service,
6138
6286
  ctx
@@ -57,8 +57,6 @@ export interface PetSettingsCardState extends CardShell {
57
57
  value: string;
58
58
  label: string;
59
59
  }[];
60
- /** Registry diagnostics (v1 migration hints, invalid entries), host-served. */
61
- petDiagnostics: readonly PetDiagnosticView[];
62
60
  }
63
61
  /** The registration-side face the card's slot entry injects. */
64
62
  export interface PetSettingsCardFace extends CardActions {
@@ -67,20 +65,16 @@ export interface PetSettingsCardFace extends CardActions {
67
65
  petSettingsCard: SnapshotStore<PetSettingsCardState>;
68
66
  };
69
67
  }
70
- /** One registry diagnostic as served by '/api/pet/diagnostics' (#623). */
71
- export interface PetDiagnosticView {
72
- level: 'error' | 'warning';
73
- message: string;
74
- }
75
68
  /** Bridges the 'pet' scope onto the card's staged form. */
76
69
  export declare class PetSettingsCardController {
77
70
  private readonly form;
78
71
  private readonly store;
79
72
  private readonly petChoices;
80
73
  private readonly petLabels;
81
- private diagnostics;
82
74
  private selectedPetId;
75
+ private selectedVisible;
83
76
  private stagedPetId;
77
+ private stagedVisible;
84
78
  private savingPet;
85
79
  private petSaveFailed;
86
80
  private loaded;
@@ -90,13 +84,23 @@ export declare class PetSettingsCardController {
90
84
  private pendingTimer;
91
85
  /** @param scope - the bound configuration form for the 'pet' entry. */
92
86
  constructor(scope: ConfigForm<PetSettings>);
93
- /** Fetch registry diagnostics once (soft-fail: an empty list on error). */
94
- private loadDiagnostics;
95
87
  /** Resolve the registry choices once (retried a few times on failure). */
96
88
  private loadPets;
97
- private loadSelectedPet;
89
+ private loadPetState;
98
90
  private fallback;
99
- private savePetSelection;
91
+ /**
92
+ * The visibility the fallback switch renders: the staged draft when the user
93
+ * moved it, the persisted value otherwise. There is no user layer to override
94
+ * here, so the field is never marked overridden.
95
+ */
96
+ private fallbackVisible;
97
+ /**
98
+ * Persist the staged selection and visibility through the pet API — the only
99
+ * writer available when the aggregate shell serves this card, since there is
100
+ * no Host settings form to mutate — then confirm the read-back before the
101
+ * drafts are cleared.
102
+ */
103
+ private saveFallback;
100
104
  private projection;
101
105
  /**
102
106
  * Build the face the card's slot registration injects.
@@ -1 +1 @@
1
- {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"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"}
@@ -9,23 +9,16 @@ async function fetchPetChoices() {
9
9
  throw new Error('pet pets failed: ' + response.status);
10
10
  return (await response.json());
11
11
  }
12
- /** Fetch the registry diagnostics (v1 migration hints, invalid entries). */
13
- async function fetchPetDiagnostics() {
14
- const response = await fetch('/api/pet/diagnostics');
15
- if (!response.ok)
16
- throw new Error('pet diagnostics failed: ' + response.status);
17
- const body = (await response.json());
18
- return body.diagnostics ?? [];
19
- }
20
- /** Read the selection from the same persisted state the pet renders. */
21
- async function fetchSelectedPetId() {
12
+ /** Read the selection and visibility from the same persisted state the pet renders. */
13
+ async function fetchPetState() {
22
14
  const response = await fetch('/api/pet/state');
23
15
  if (!response.ok)
24
16
  throw new Error('pet state failed: ' + response.status);
25
17
  const body = (await response.json());
26
18
  if (typeof body.pet?.id !== 'string')
27
19
  throw new Error('pet state has no selected pet');
28
- return body.pet.id;
20
+ // A Host without the display block predates the visibility switch; the pet shows.
21
+ return { petId: body.pet.id, visible: body.display?.visible !== false };
29
22
  }
30
23
  /** Bridges the 'pet' scope onto the card's staged form. */
31
24
  export class PetSettingsCardController {
@@ -36,9 +29,10 @@ export class PetSettingsCardController {
36
29
  // without rebuilding the form.
37
30
  petChoices = [];
38
31
  petLabels = new Map();
39
- diagnostics = [];
40
32
  selectedPetId;
33
+ selectedVisible;
41
34
  stagedPetId;
35
+ stagedVisible;
42
36
  savingPet = false;
43
37
  petSaveFailed = false;
44
38
  loaded = false;
@@ -68,22 +62,9 @@ export class PetSettingsCardController {
68
62
  if (this.disposed)
69
63
  return;
70
64
  void this.loadPets();
71
- void this.loadDiagnostics();
72
- void this.loadSelectedPet();
65
+ void this.loadPetState();
73
66
  }, 0);
74
67
  }
75
- /** Fetch registry diagnostics once (soft-fail: an empty list on error). */
76
- async loadDiagnostics() {
77
- try {
78
- this.diagnostics = await fetchPetDiagnostics();
79
- if (this.disposed)
80
- return;
81
- this.store.set(this.projection());
82
- }
83
- catch {
84
- this.diagnostics = [];
85
- }
86
- }
87
68
  /** Resolve the registry choices once (retried a few times on failure). */
88
69
  async loadPets() {
89
70
  if (this.loaded || this.disposed)
@@ -112,12 +93,13 @@ export class PetSettingsCardController {
112
93
  }
113
94
  }
114
95
  }
115
- async loadSelectedPet() {
96
+ async loadPetState() {
116
97
  try {
117
- const petId = await fetchSelectedPetId();
98
+ const state = await fetchPetState();
118
99
  if (this.disposed)
119
100
  return;
120
- this.selectedPetId = petId;
101
+ this.selectedPetId = state.petId;
102
+ this.selectedVisible = state.visible;
121
103
  this.store.set(this.projection());
122
104
  }
123
105
  catch {
@@ -129,26 +111,65 @@ export class PetSettingsCardController {
129
111
  const shell = this.form.shell();
130
112
  return shell.available && !shell.exposed && this.selectedPetId !== undefined;
131
113
  }
132
- async savePetSelection() {
114
+ /**
115
+ * The visibility the fallback switch renders: the staged draft when the user
116
+ * moved it, the persisted value otherwise. There is no user layer to override
117
+ * here, so the field is never marked overridden.
118
+ */
119
+ fallbackVisible() {
120
+ const value = this.stagedVisible ?? this.selectedVisible;
121
+ return { text: value === undefined ? '' : String(value), overridden: false, invalid: false };
122
+ }
123
+ /**
124
+ * Persist the staged selection and visibility through the pet API — the only
125
+ * writer available when the aggregate shell serves this card, since there is
126
+ * no Host settings form to mutate — then confirm the read-back before the
127
+ * drafts are cleared.
128
+ */
129
+ async saveFallback() {
133
130
  const petId = this.stagedPetId;
134
- if (petId === undefined || this.savingPet || !this.petChoices.includes(petId))
131
+ const visible = this.stagedVisible;
132
+ if (this.savingPet)
133
+ return;
134
+ if (petId === undefined && visible === undefined)
135
+ return;
136
+ if (petId !== undefined && !this.petChoices.includes(petId))
135
137
  return;
136
138
  this.savingPet = true;
137
139
  this.petSaveFailed = false;
138
140
  this.store.set(this.projection());
139
141
  try {
140
- const response = await fetch('/api/pet/set-pet', {
141
- method: 'POST',
142
- headers: { 'content-type': 'application/json' },
143
- body: JSON.stringify({ petId }),
144
- });
145
- const result = (await response.json());
146
- if (!response.ok || result.ok !== true || result.petId !== petId || await fetchSelectedPetId() !== petId) {
147
- throw new Error('pet selection was not persisted');
142
+ if (petId !== undefined) {
143
+ const response = await fetch('/api/pet/set-pet', {
144
+ method: 'POST',
145
+ headers: { 'content-type': 'application/json' },
146
+ body: JSON.stringify({ petId }),
147
+ });
148
+ const result = (await response.json());
149
+ if (!response.ok || result.ok !== true || result.petId !== petId) {
150
+ throw new Error('pet selection was not persisted');
151
+ }
152
+ }
153
+ if (visible !== undefined) {
154
+ const response = await fetch('/api/pet/set-visible', {
155
+ method: 'POST',
156
+ headers: { 'content-type': 'application/json' },
157
+ body: JSON.stringify({ visible }),
158
+ });
159
+ if (!response.ok)
160
+ throw new Error('pet visibility was not persisted');
148
161
  }
149
- this.selectedPetId = petId;
162
+ const persisted = await fetchPetState();
163
+ if (petId !== undefined && persisted.petId !== petId)
164
+ throw new Error('pet selection was not persisted');
165
+ if (visible !== undefined && persisted.visible !== visible)
166
+ throw new Error('pet visibility was not persisted');
167
+ this.selectedPetId = persisted.petId;
168
+ this.selectedVisible = persisted.visible;
150
169
  if (this.stagedPetId === petId)
151
170
  this.stagedPetId = undefined;
171
+ if (this.stagedVisible === visible)
172
+ this.stagedVisible = undefined;
152
173
  }
153
174
  catch {
154
175
  this.petSaveFailed = true;
@@ -168,7 +189,8 @@ export class PetSettingsCardController {
168
189
  ...(fallback ? {
169
190
  exposed: true,
170
191
  writable: true,
171
- dirty: this.stagedPetId !== undefined && this.stagedPetId !== this.selectedPetId,
192
+ dirty: this.stagedPetId !== undefined && this.stagedPetId !== this.selectedPetId
193
+ || this.stagedVisible !== undefined && this.stagedVisible !== this.selectedVisible,
172
194
  invalid: this.stagedPetId !== undefined && !this.petChoices.includes(this.stagedPetId),
173
195
  saving: this.savingPet,
174
196
  failed: this.petSaveFailed,
@@ -177,7 +199,7 @@ export class PetSettingsCardController {
177
199
  petSelectionFallback: fallback,
178
200
  enabled: this.form.field('enabled'),
179
201
  decorationEnabled: this.form.field('decorationEnabled'),
180
- visible: this.form.field('visible'),
202
+ visible: fallback ? this.fallbackVisible() : this.form.field('visible'),
181
203
  size: this.form.field('size'),
182
204
  right: this.form.field('right'),
183
205
  bottom: this.form.field('bottom'),
@@ -188,7 +210,6 @@ export class PetSettingsCardController {
188
210
  ? { ...configuredPet, text: this.selectedPetId }
189
211
  : configuredPet,
190
212
  petChoices: this.petChoices.map(id => ({ value: id, label: this.petLabels.get(id) ?? id })),
191
- petDiagnostics: this.diagnostics,
192
213
  };
193
214
  }
194
215
  /**
@@ -202,29 +223,36 @@ export class PetSettingsCardController {
202
223
  edit: (field, value) => {
203
224
  if (!this.fallback())
204
225
  return actions.edit(field, value);
205
- if (field !== 'petId')
226
+ if (field === 'petId')
227
+ this.stagedPetId = value === '' ? undefined : value;
228
+ else if (field === 'visible')
229
+ this.stagedVisible = value === '' ? undefined : value === 'true';
230
+ else
206
231
  return;
207
- this.stagedPetId = value === '' ? undefined : value;
208
232
  this.petSaveFailed = false;
209
233
  this.store.set(this.projection());
210
234
  },
211
235
  resetField: (field) => {
212
236
  if (!this.fallback())
213
237
  return actions.resetField(field);
214
- if (field !== 'petId')
238
+ if (field === 'petId')
239
+ this.stagedPetId = undefined;
240
+ else if (field === 'visible')
241
+ this.stagedVisible = undefined;
242
+ else
215
243
  return;
216
- this.stagedPetId = undefined;
217
244
  this.petSaveFailed = false;
218
245
  this.store.set(this.projection());
219
246
  },
220
247
  save: () => { if (this.fallback())
221
- void this.savePetSelection();
248
+ void this.saveFallback();
222
249
  else
223
250
  actions.save(); },
224
251
  discard: () => {
225
252
  if (!this.fallback())
226
253
  return actions.discard();
227
254
  this.stagedPetId = undefined;
255
+ this.stagedVisible = undefined;
228
256
  this.petSaveFailed = false;
229
257
  this.store.set(this.projection());
230
258
  },
@@ -260,7 +288,7 @@ export function PetSettingsCard(props) {
260
288
  invalidLabel: t('settings.invalidNumber'),
261
289
  disabled,
262
290
  };
263
- return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", descriptionNode: state.petSelectionFallback ? t('settings.petHint') : undefined, state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [state.petSelectionFallback ? null : _jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), state.petSelectionFallback ? null : _jsx(BooleanField, { id: "settings-pet-decoration", label: t('settings.decoration'), hint: t('settings.decorationHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.decorationEnabled, onEdit: (text) => { props.edit('decorationEnabled', text); }, onReset: () => { props.resetField('decorationEnabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), state.petDiagnostics.length === 0 ? null : (_jsxs("li", { className: sectionCss.diagnostics, "data-dsh-part": "diagnostics", children: [_jsx("span", { className: sectionCss.diagnosticsTitle, children: t('settings.diagnosticsTitle') }), _jsx("ul", { children: state.petDiagnostics.map((diagnostic, index) => (_jsx("li", { "data-level": diagnostic.level, children: diagnostic.message }, index))) })] })), state.petSelectionFallback ? null : _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-bubble-scale", label: t('settings.bubbleScale'), hint: t('settings.bubbleScaleHint'), numeric: true, ...fieldProps, ...state.bubbleScale, onEdit: (text) => { props.edit('bubbleScale', text); }, onReset: () => { props.resetField('bubbleScale'); } })] }));
291
+ return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", descriptionNode: state.petSelectionFallback ? t('settings.petHint') : undefined, state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [state.petSelectionFallback ? null : _jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), state.petSelectionFallback ? null : _jsx(BooleanField, { id: "settings-pet-decoration", label: t('settings.decoration'), hint: t('settings.decorationHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.decorationEnabled, onEdit: (text) => { props.edit('decorationEnabled', text); }, onReset: () => { props.resetField('decorationEnabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } }), state.petSelectionFallback ? null : _jsx(ValueField, { id: "settings-pet-bubble-scale", label: t('settings.bubbleScale'), hint: t('settings.bubbleScaleHint'), numeric: true, ...fieldProps, ...state.bubbleScale, onEdit: (text) => { props.edit('bubbleScale', text); }, onReset: () => { props.resetField('bubbleScale'); } })] }));
264
292
  }
265
293
  /** Render the pet settings card as a first-level settings page. */
266
294
  export function PetSettingsSection(props) {
@@ -53,7 +53,6 @@ export declare const zh: {
53
53
  readonly 'pet.moreSessions': "展开其余 {n} 个会话的气泡";
54
54
  readonly 'pet.collapseSessions': "收起会话气泡";
55
55
  readonly 'settings.title': "宠物";
56
- readonly 'settings.diagnosticsTitle': "宠物目录诊断";
57
56
  readonly 'settings.description': "选择宠物并调整它的显示布局。";
58
57
  readonly 'settings.pet': "宠物";
59
58
  readonly 'settings.petHint': "选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。";
@@ -136,7 +135,6 @@ export declare const en: {
136
135
  readonly 'pet.moreSessions': "Expand {n} more session bubbles";
137
136
  readonly 'pet.collapseSessions': "Collapse session bubbles";
138
137
  readonly 'settings.title': "Pet";
139
- readonly 'settings.diagnosticsTitle': "Pet directory diagnostics";
140
138
  readonly 'settings.description': "Pick a pet and tune its display layout.";
141
139
  readonly 'settings.pet': "Pet";
142
140
  readonly 'settings.petHint': "Choose which pet shows. Names are stored per pet; rename from the pet hover panel.";
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmFL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmFL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQvE;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQvE;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}
@@ -55,7 +55,6 @@ export const zh = {
55
55
  'pet.collapseSessions': '收起会话气泡',
56
56
  // 一级设置页(settings.section 席位)。
57
57
  'settings.title': '宠物',
58
- 'settings.diagnosticsTitle': '宠物目录诊断',
59
58
  'settings.description': '选择宠物并调整它的显示布局。',
60
59
  'settings.pet': '宠物',
61
60
  'settings.petHint': '选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。',
@@ -140,7 +139,6 @@ export const en = {
140
139
  'pet.collapseSessions': 'Collapse session bubbles',
141
140
  // First-level settings section (the `settings.section` seat).
142
141
  'settings.title': 'Pet',
143
- 'settings.diagnosticsTitle': 'Pet directory diagnostics',
144
142
  'settings.description': 'Pick a pet and tune its display layout.',
145
143
  'settings.pet': 'Pet',
146
144
  'settings.petHint': 'Choose which pet shows. Names are stored per pet; rename from the pet hover panel.',
@@ -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. */