@deepseek-ai/dsh-client-ui-settings 0.1.6-alpha.2 → 0.1.7-alpha.2

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
@@ -6,7 +6,7 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
8
8
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
9
- //#region ../../../vendor/cosmokit/src/misc.ts
9
+ //#region ../../../vendor/cosmokit/lib/index.js
10
10
  /** Return true when a value is `null` or `undefined`. */
11
11
  function isNullable(value) {
12
12
  return value === null || value === void 0;
@@ -30,8 +30,43 @@ window.__ModuleLoader__.load({
30
30
  for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
31
31
  return result;
32
32
  }
33
- //#endregion
34
- //#region ../../../vendor/cosmokit/src/types.ts
33
+ /** Shared config references used by schema validators and plugin runtimes. */
34
+ const write = Symbol.for("cosmokit.volatile.write");
35
+ function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
36
+ if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
37
+ if (value === null || typeof value !== "object") return value;
38
+ if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
39
+ ancestors.add(value);
40
+ try {
41
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
42
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
43
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
44
+ } finally {
45
+ ancestors.delete(value);
46
+ }
47
+ }
48
+ /**
49
+ * Create a detached reference containing an immutable copy of the supplied data.
50
+ * @param value - validated config data; class instances and functions are unsupported.
51
+ * @returns a reference whose value is updated only by its owning runtime.
52
+ */
53
+ function createVolatile(value) {
54
+ let current = snapshot(value);
55
+ return Object.freeze({
56
+ get: () => current,
57
+ [write]: (value) => {
58
+ current = value;
59
+ }
60
+ });
61
+ }
62
+ /**
63
+ * Identify references across ESM/CJS copies of the shared library.
64
+ * @param value - a parsed config value.
65
+ * @returns whether the value implements the shared reference protocol.
66
+ */
67
+ function isVolatile(value) {
68
+ return typeof value === "object" && value !== null && write in value;
69
+ }
35
70
  /** Test values using `instanceof` with a `toStringTag` fallback. */
36
71
  function is(type, value) {
37
72
  if (arguments.length === 1) return (value) => is(type, value);
@@ -43,15 +78,16 @@ window.__ModuleLoader__.load({
43
78
  function isArrayBufferSource(value) {
44
79
  return isArrayBufferLike(value) || ArrayBuffer.isView(value);
45
80
  }
46
- let Binary;
47
- (function(_Binary) {
48
- _Binary.is = isArrayBufferLike;
49
- _Binary.isSource = isArrayBufferSource;
81
+ /** Binary source detection and base64/hex conversion helpers. */
82
+ var Binary;
83
+ (function(Binary) {
84
+ Binary.is = isArrayBufferLike;
85
+ Binary.isSource = isArrayBufferSource;
50
86
  function fromSource(source) {
51
87
  if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
52
88
  else return source;
53
89
  }
54
- _Binary.fromSource = fromSource;
90
+ Binary.fromSource = fromSource;
55
91
  function toBase64(source) {
56
92
  source = fromSource(source);
57
93
  if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
@@ -60,18 +96,18 @@ window.__ModuleLoader__.load({
60
96
  for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
61
97
  return btoa(binary);
62
98
  }
63
- _Binary.toBase64 = toBase64;
99
+ Binary.toBase64 = toBase64;
64
100
  function fromBase64(source) {
65
101
  if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
66
102
  return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
67
103
  }
68
- _Binary.fromBase64 = fromBase64;
104
+ Binary.fromBase64 = fromBase64;
69
105
  function toHex(source) {
70
106
  source = fromSource(source);
71
107
  if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
72
108
  return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
73
109
  }
74
- _Binary.toHex = toHex;
110
+ Binary.toHex = toHex;
75
111
  function fromHex(source) {
76
112
  if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
77
113
  const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
@@ -79,7 +115,7 @@ window.__ModuleLoader__.load({
79
115
  for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
80
116
  return Uint8Array.from(buffer).buffer;
81
117
  }
82
- _Binary.fromHex = fromHex;
118
+ Binary.fromHex = fromHex;
83
119
  })(Binary || (Binary = {}));
84
120
  Binary.fromBase64;
85
121
  Binary.toBase64;
@@ -111,58 +147,78 @@ window.__ModuleLoader__.load({
111
147
  }
112
148
  return result;
113
149
  }
114
- /** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
150
+ /**
151
+ * Compare values recursively, treating two volatile references as equal regardless of value.
152
+ * Strict comparison distinguishes null/undefined, treats opaque objects by identity,
153
+ * compares URLs by normalized href, treats array holes as undefined, and considers distinct cyclic structures unequal.
154
+ * @param a - first value.
155
+ * @param b - second value.
156
+ * @param strict - whether to require strict data equality outside volatile references.
157
+ * @returns whether the values compare equal.
158
+ */
115
159
  function deepEqual(a, b, strict) {
116
- if (a === b) return true;
117
- if (!strict && isNullable(a) && isNullable(b)) return true;
118
- if (typeof a !== typeof b) return false;
119
- if (typeof a !== "object") return false;
120
- if (!a || !b) return false;
121
- function check(test, then) {
122
- return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
123
- }
124
- return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
125
- if (a.byteLength !== b.byteLength) return false;
126
- const viewA = new Uint8Array(a);
127
- const viewB = new Uint8Array(b);
128
- for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
129
- return true;
130
- }) ?? Object.keys({
131
- ...a,
132
- ...b
133
- }).every((key) => deepEqual(a[key], b[key], strict));
160
+ const ancestors = /* @__PURE__ */ new Set();
161
+ function compare(a, b) {
162
+ if (a === b) return true;
163
+ if (isVolatile(a) || isVolatile(b)) return isVolatile(a) && isVolatile(b);
164
+ if (!strict && isNullable(a) && isNullable(b)) return true;
165
+ if (typeof a !== typeof b || typeof a !== "object" || !a || !b) return false;
166
+ if (ancestors.has(a)) return false;
167
+ function check(test, then) {
168
+ return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
169
+ }
170
+ ancestors.add(a);
171
+ try {
172
+ return check(Array.isArray, (a, b) => {
173
+ if (a.length !== b.length) return false;
174
+ for (let index = 0; index < a.length; index++) if (!compare(a[index], b[index])) return false;
175
+ return true;
176
+ }) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("URL"), (a, b) => a.href === b.href) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
177
+ if (a.byteLength !== b.byteLength) return false;
178
+ const viewA = new Uint8Array(a);
179
+ const viewB = new Uint8Array(b);
180
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
181
+ return true;
182
+ }) ?? ((!strict || [a, b].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
183
+ ...a,
184
+ ...b
185
+ }).every((key) => compare(a[key], b[key])));
186
+ } finally {
187
+ ancestors.delete(a);
188
+ }
189
+ }
190
+ return compare(a, b);
134
191
  }
135
- //#endregion
136
- //#region ../../../vendor/cosmokit/src/time.ts
137
- let Time;
138
- (function(_Time) {
139
- _Time.millisecond = 1;
140
- const second = _Time.second = 1e3;
141
- const minute = _Time.minute = second * 60;
142
- const hour = _Time.hour = minute * 60;
143
- const day = _Time.day = hour * 24;
144
- const week = _Time.week = day * 7;
192
+ /** Time constants plus parsing and formatting helpers. */
193
+ var Time;
194
+ (function(Time) {
195
+ Time.millisecond = 1;
196
+ Time.second = 1e3;
197
+ Time.minute = Time.second * 60;
198
+ Time.hour = Time.minute * 60;
199
+ Time.day = Time.hour * 24;
200
+ Time.week = Time.day * 7;
145
201
  let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
146
202
  function setTimezoneOffset(offset) {
147
203
  timezoneOffset = offset;
148
204
  }
149
- _Time.setTimezoneOffset = setTimezoneOffset;
205
+ Time.setTimezoneOffset = setTimezoneOffset;
150
206
  function getTimezoneOffset() {
151
207
  return timezoneOffset;
152
208
  }
153
- _Time.getTimezoneOffset = getTimezoneOffset;
209
+ Time.getTimezoneOffset = getTimezoneOffset;
154
210
  function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
155
211
  if (typeof date === "number") date = new Date(date);
156
212
  if (offset === void 0) offset = timezoneOffset;
157
- return Math.floor((date.valueOf() / minute - offset) / 1440);
213
+ return Math.floor((date.valueOf() / Time.minute - offset) / 1440);
158
214
  }
159
- _Time.getDateNumber = getDateNumber;
215
+ Time.getDateNumber = getDateNumber;
160
216
  function fromDateNumber(value, offset) {
161
- const date = new Date(value * day);
217
+ const date = new Date(value * Time.day);
162
218
  if (offset === void 0) offset = timezoneOffset;
163
- return new Date(+date + offset * minute);
219
+ return new Date(+date + offset * Time.minute);
164
220
  }
165
- _Time.fromDateNumber = fromDateNumber;
221
+ Time.fromDateNumber = fromDateNumber;
166
222
  const numeric = /\d+(?:\.\d+)?/.source;
167
223
  const timeRegExp = new RegExp(`^${[
168
224
  "w(?:eek(?:s)?)?",
@@ -174,9 +230,9 @@ window.__ModuleLoader__.load({
174
230
  function parseTime(source) {
175
231
  const capture = timeRegExp.exec(source);
176
232
  if (!capture) return 0;
177
- return (parseFloat(capture[1]) * week || 0) + (parseFloat(capture[2]) * day || 0) + (parseFloat(capture[3]) * hour || 0) + (parseFloat(capture[4]) * minute || 0) + (parseFloat(capture[5]) * second || 0);
233
+ return (parseFloat(capture[1]) * Time.week || 0) + (parseFloat(capture[2]) * Time.day || 0) + (parseFloat(capture[3]) * Time.hour || 0) + (parseFloat(capture[4]) * Time.minute || 0) + (parseFloat(capture[5]) * Time.second || 0);
178
234
  }
179
- _Time.parseTime = parseTime;
235
+ Time.parseTime = parseTime;
180
236
  function parseDate(date) {
181
237
  const parsed = parseTime(date);
182
238
  if (parsed) date = Date.now() + parsed;
@@ -184,27 +240,27 @@ window.__ModuleLoader__.load({
184
240
  else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
185
241
  return date ? new Date(date) : /* @__PURE__ */ new Date();
186
242
  }
187
- _Time.parseDate = parseDate;
243
+ Time.parseDate = parseDate;
188
244
  function format(ms) {
189
245
  const abs = Math.abs(ms);
190
- if (abs >= day - hour / 2) return Math.round(ms / day) + "d";
191
- else if (abs >= hour - minute / 2) return Math.round(ms / hour) + "h";
192
- else if (abs >= minute - second / 2) return Math.round(ms / minute) + "m";
193
- else if (abs >= second) return Math.round(ms / second) + "s";
246
+ if (abs >= Time.day - Time.hour / 2) return Math.round(ms / Time.day) + "d";
247
+ else if (abs >= Time.hour - Time.minute / 2) return Math.round(ms / Time.hour) + "h";
248
+ else if (abs >= Time.minute - Time.second / 2) return Math.round(ms / Time.minute) + "m";
249
+ else if (abs >= Time.second) return Math.round(ms / Time.second) + "s";
194
250
  return ms + "ms";
195
251
  }
196
- _Time.format = format;
252
+ Time.format = format;
197
253
  function toDigits(source, length = 2) {
198
254
  return source.toString().padStart(length, "0");
199
255
  }
200
- _Time.toDigits = toDigits;
256
+ Time.toDigits = toDigits;
201
257
  function template(template, time = /* @__PURE__ */ new Date()) {
202
258
  return template.replace("yyyy", time.getFullYear().toString()).replace("yy", time.getFullYear().toString().slice(2)).replace("MM", toDigits(time.getMonth() + 1)).replace("dd", toDigits(time.getDate())).replace("hh", toDigits(time.getHours())).replace("mm", toDigits(time.getMinutes())).replace("ss", toDigits(time.getSeconds())).replace("SSS", toDigits(time.getMilliseconds(), 3));
203
259
  }
204
- _Time.template = template;
260
+ Time.template = template;
205
261
  })(Time || (Time = {}));
206
262
  //#endregion
207
- //#region ../../../vendor/schemastery/src/index.ts
263
+ //#region ../../../vendor/schemastery/lib/index.mjs
208
264
  const kSchema = Symbol.for("schemastery");
209
265
  const kValidationError = Symbol.for("ValidationError");
210
266
  globalThis.__schemastery_index__ ??= 0;
@@ -380,6 +436,7 @@ window.__ModuleLoader__.load({
380
436
  return schema;
381
437
  };
382
438
  Schema.prototype.simplify = function simplify(value) {
439
+ if (isVolatile(value)) value = value.get();
383
440
  if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
384
441
  if (isNullable(value)) return value;
385
442
  if (this.type === "object" || this.type === "dict") {
@@ -436,12 +493,49 @@ window.__ModuleLoader__.load({
436
493
  };
437
494
  return schema;
438
495
  } });
496
+ Schema.prototype.volatile = function volatile() {
497
+ if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
498
+ return this.extra("volatile", true);
499
+ };
439
500
  const resolvers = {};
501
+ const checkedVolatile = Symbol("checked-volatile-schema");
502
+ function validateVolatileSchema(schema, path = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
503
+ const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
504
+ if (states.has(blocked)) return;
505
+ states.add(blocked);
506
+ seen.set(schema, states);
507
+ if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path });
508
+ const nested = blocked || !!schema.meta?.volatile;
509
+ if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen);
510
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, "<key>"], true, seen);
511
+ if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path, "*"], true, seen);
512
+ if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path, String(index)], true, seen);
513
+ }
440
514
  Schema.extend = function extend(type, resolve) {
441
515
  resolvers[type] = resolve;
442
516
  };
443
517
  Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
444
518
  if (!schema) return [data];
519
+ if (!options[checkedVolatile]) {
520
+ validateVolatileSchema(schema, options.path);
521
+ options = {
522
+ ...options,
523
+ [checkedVolatile]: true
524
+ };
525
+ }
526
+ if (schema.meta?.volatile) {
527
+ const inner = Schema(schema);
528
+ inner.meta = {
529
+ ...schema.meta,
530
+ volatile: false
531
+ };
532
+ const [value, adapted] = Schema.resolve(data, inner, options, strict);
533
+ try {
534
+ return [createVolatile(value), adapted];
535
+ } catch (error) {
536
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options);
537
+ }
538
+ }
445
539
  if (options.ignore?.(data, schema)) return [data];
446
540
  if (isNullable(data) && schema.type !== "lazy") {
447
541
  if (schema.meta.required) throw new ValidationError(`missing required value`, options);
@@ -544,6 +638,7 @@ window.__ModuleLoader__.load({
544
638
  ...schema.meta,
545
639
  ...schema.inner.meta
546
640
  };
641
+ validateVolatileSchema(schema.inner, options.path, true);
547
642
  }
548
643
  return Schema.resolve(data, schema.inner, options, strict);
549
644
  });
@@ -643,7 +738,7 @@ window.__ModuleLoader__.load({
643
738
  } catch (e) {
644
739
  if (!options?.autofix) throw e;
645
740
  delete data[key];
646
- return schema.meta.default;
741
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
647
742
  }
648
743
  }
649
744
  Schema.extend("array", (data, { inner, meta }, options) => {
@@ -932,21 +1027,63 @@ window.__ModuleLoader__.load({
932
1027
  }
933
1028
  };
934
1029
  //#endregion
935
- //#region lib/types/client/settings-scope.js
936
- /**
937
- * Host transport for the settings-namespace scope contract. This file owns the
938
- * per-namespace derivation over the shared {@link SettingsDescribeMirror} and
939
- * the serialized write path. Reads never touch the wire here: the
940
- * mirror is the one `settings.describe` reader, and every scope is a selector
941
- * over its snapshot.
942
- */
1030
+ //#region lib/types/client/developer-tools.js
1031
+ /** One accepted preference drives every developer-tool consumer. */
1032
+ /** Shared preference; Host-backed features stay disabled until an accepted value arrives. */
1033
+ var DeveloperToolsPreference = class {
1034
+ scope;
1035
+ /** Accepted enablement, observable through renderer-bound hooks. */
1036
+ enabled;
1037
+ local = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(true);
1038
+ /**
1039
+ * @param scope - settings-owned namespace controller.
1040
+ */
1041
+ constructor(scope) {
1042
+ this.scope = scope;
1043
+ this.enabled = scope.getSnapshot().mode === "memory" ? this.local : {
1044
+ getSnapshot: () => scope.getSnapshot().value?.enabled ?? false,
1045
+ subscribe: (listener) => {
1046
+ let previous = this.enabled.getSnapshot();
1047
+ return scope.subscribe(() => {
1048
+ const next = this.enabled.getSnapshot();
1049
+ if (next === previous) return;
1050
+ previous = next;
1051
+ listener();
1052
+ });
1053
+ }
1054
+ };
1055
+ }
1056
+ /**
1057
+ * Persist a Host choice with ordered writes, or update the shared browser-local choice.
1058
+ * @param enabled - requested developer-tool mode.
1059
+ * @returns settlement after local publication or Host acceptance; rejects after a refused write recovers.
1060
+ */
1061
+ async setEnabled(enabled) {
1062
+ if (this.scope.getSnapshot().mode === "memory") {
1063
+ this.local.set(enabled);
1064
+ return;
1065
+ }
1066
+ if (!await this.scope.set("enabled", enabled)) throw new Error("Developer tools preference was not saved");
1067
+ }
1068
+ };
1069
+ //#endregion
1070
+ //#region lib/types/developer-tools-settings.js
1071
+ /** Shared Web and desktop developer-tool preference stored by the Host. */
1072
+ /** Namespace for developer UI and HTML preview capabilities. */
1073
+ const DEVELOPER_TOOLS_NAMESPACE = "ui-settings";
1074
+ /** New installations and missing values enable the full interface. */
1075
+ const DeveloperToolsSettingsFields = { enabled: Schema.boolean().default(true) };
1076
+ Schema.object(DeveloperToolsSettingsFields);
1077
+ //#endregion
1078
+ //#region lib/types/client/config-form.js
1079
+ /** Shared entry values and ordered writes over the Host configuration mirror. */
943
1080
  /**
944
1081
  * One namespace's derived view over the shared describe mirror, plus that
945
1082
  * namespace's serialized Host writes. Writes carry the latest known namespace
946
1083
  * revision, fold their answers back into the mirror, and teardown waits for
947
1084
  * the operation already crossing the wire.
948
1085
  */
949
- var SettingsScopeController = class {
1086
+ var ConfigFormController = class {
950
1087
  ctx;
951
1088
  spec;
952
1089
  mirror;
@@ -965,9 +1102,9 @@ window.__ModuleLoader__.load({
965
1102
  pendingRevision;
966
1103
  /**
967
1104
  * @param ctx - the providing plugin's context, whose `remote.settings`
968
- * namespace carries this scope's writes (reads ride the mirror).
1105
+ * namespace carries this form's writes (reads ride the mirror).
969
1106
  * @param spec - namespace identity and optional narrowing decoder.
970
- * @param mirror - the shared describe mirror this scope derives from.
1107
+ * @param mirror - the shared describe mirror this form derives from.
971
1108
  * @param persistence - client-selected Host persistence; non-loopback pages may remain process-local.
972
1109
  * @param schema - settings-owned schema operations.
973
1110
  */
@@ -1006,11 +1143,11 @@ window.__ModuleLoader__.load({
1006
1143
  return this.store.subscribe(listener);
1007
1144
  }
1008
1145
  /**
1009
- * Queue one field write; see {@link SettingsScope.set} for the ordering,
1146
+ * Queue one field write; see {@link ConfigForm.set} for the ordering,
1010
1147
  * revision, and recovery contract.
1011
1148
  * @param field - scalar field inside the namespace section.
1012
1149
  * @param value - JSON-shaped value selected by the user.
1013
- * @returns settlement after the write and any latest-write recovery read.
1150
+ * @returns whether the Host accepted the write, after any recovery read.
1014
1151
  */
1015
1152
  set(field, value) {
1016
1153
  return this.mutate([{
@@ -1020,10 +1157,10 @@ window.__ModuleLoader__.load({
1020
1157
  }]);
1021
1158
  }
1022
1159
  /**
1023
- * Queue one field clear; see {@link SettingsScope.unset} for the ordering,
1160
+ * Queue one field clear; see {@link ConfigForm.unset} for the ordering,
1024
1161
  * revision, and recovery contract.
1025
1162
  * @param field - scalar field inside the namespace section.
1026
- * @returns settlement after the clear and any latest-write recovery read.
1163
+ * @returns whether the Host accepted the clear, after any recovery read.
1027
1164
  */
1028
1165
  unset(field) {
1029
1166
  return this.mutate([{
@@ -1032,10 +1169,10 @@ window.__ModuleLoader__.load({
1032
1169
  }]);
1033
1170
  }
1034
1171
  /**
1035
- * Queue one atomic namespace mutation; see {@link SettingsScope.mutate}.
1172
+ * Queue one atomic namespace mutation; see {@link ConfigForm.mutate}.
1036
1173
  * @param ops - ordered field operations copied when queued.
1037
1174
  * @param expectedRevision - optional fixed revision read by the domain editor.
1038
- * @returns settlement after the mutation and any latest-write recovery read.
1175
+ * @returns whether the Host accepted the mutation, after any recovery read.
1039
1176
  */
1040
1177
  mutate(ops, expectedRevision) {
1041
1178
  const ownedOps = structuredClone(ops);
@@ -1045,13 +1182,14 @@ window.__ModuleLoader__.load({
1045
1182
  const response = await this.ctx.remote.settings.mutate(this.spec.namespace, ownedOps, revision);
1046
1183
  if (!response.ok) {
1047
1184
  await this.recover(generation);
1048
- return;
1185
+ return false;
1049
1186
  }
1050
- if (this.disposed) return;
1187
+ if (this.disposed) return true;
1051
1188
  if (generation === this.writeGeneration) {
1052
1189
  this.pendingRevision = void 0;
1053
1190
  this.mirror.acceptView(response.value);
1054
1191
  } else this.pendingRevision = response.value.revision;
1192
+ return true;
1055
1193
  });
1056
1194
  }
1057
1195
  /** Reload Host state for the latest failed write; superseded failures leave recovery to it. */
@@ -1072,12 +1210,12 @@ window.__ModuleLoader__.load({
1072
1210
  await this.tail;
1073
1211
  }
1074
1212
  enqueue(operation) {
1075
- if (this.persistence === "memory" || this.disposed) return Promise.resolve();
1213
+ if (this.persistence === "memory" || this.disposed) return Promise.resolve(false);
1076
1214
  const task = this.tail.then(async () => {
1077
- if (this.disposed) return;
1078
- await operation();
1215
+ if (this.disposed) return false;
1216
+ return await operation();
1079
1217
  });
1080
- this.tail = task.catch(() => {});
1218
+ this.tail = task.then(() => {}, () => {});
1081
1219
  return task;
1082
1220
  }
1083
1221
  derive() {
@@ -1123,59 +1261,91 @@ window.__ModuleLoader__.load({
1123
1261
  * cross-plugin collaboration through cordis services
1124
1262
  * (`packages/client/tsdown.client.ts`).
1125
1263
  */
1126
- var SettingsScopeBinder = class extends _deepseek_ai_cordis.Service {
1264
+ var ConfigForms = class extends _deepseek_ai_cordis.Service {
1265
+ forms = /* @__PURE__ */ new Map();
1266
+ /** Shared developer-tool preference owned by this settings provider. */
1267
+ developerTools;
1127
1268
  mirror;
1128
1269
  schema;
1129
1270
  persistence;
1130
1271
  /**
1131
1272
  * The PROVIDING fiber, kept because a Service reads `ctx` as its *consumer's*
1132
- * fiber: letting a bound scope write through the caller's context would make
1273
+ * fiber: letting a shared form write through the caller's context would make
1133
1274
  * every caller declare `remote.settings` in its own `inject`.
1134
1275
  */
1135
1276
  owner;
1136
1277
  /**
1137
1278
  * @param ctx - the providing plugin's context.
1138
- * @param config - the shared describe mirror every bound scope derives from,
1279
+ * @param config - the shared describe mirror every shared form derives from,
1139
1280
  * the settings-owned schema operations, and the Host persistence the provider
1140
1281
  * resolved from `remote.$host`.
1141
1282
  */
1142
1283
  constructor(ctx, config) {
1143
- super(ctx, "settingsScope");
1284
+ super(ctx, "configForms");
1144
1285
  this.mirror = config.mirror;
1145
1286
  this.schema = config.schema;
1146
1287
  this.persistence = config.persistence;
1147
1288
  this.owner = ctx;
1289
+ this.developerTools = new DeveloperToolsPreference(this.get(DEVELOPER_TOOLS_NAMESPACE));
1290
+ ctx.effect(() => async () => {
1291
+ await Promise.all([...this.forms.values()].map((form) => form.dispose()));
1292
+ this.forms.clear();
1293
+ }, "ui-settings: configuration forms");
1148
1294
  }
1149
1295
  /**
1150
1296
  * The shared mirror's read/fold face for cross-namespace surfaces (schema
1151
1297
  * introspection, the served-namespace directory). Per-namespace consumers
1152
- * use {@link bind}; both derive from the same snapshot, so they can never
1298
+ * use {@link get}; both derive from the same snapshot, so they can never
1153
1299
  * disagree about the document.
1154
1300
  * @returns the describe face over the shared mirror.
1155
1301
  */
1156
1302
  describe() {
1157
1303
  return this.mirror;
1158
1304
  }
1305
+ /** Get the shared form values and write queue for one Host plugin entry.
1306
+ * @param entryId Unique Host plugin entry id.
1307
+ * @returns The entry's form, owned by this provider.
1308
+ */
1309
+ get(entryId) {
1310
+ const existing = this.forms.get(entryId);
1311
+ if (existing !== void 0) return existing;
1312
+ const form = new ConfigFormController(this.owner, { namespace: entryId }, this.mirror, this.persistence, this.schema);
1313
+ this.forms.set(entryId, form);
1314
+ this.mirror.ensure();
1315
+ return form;
1316
+ }
1159
1317
  /**
1160
- * Bind one namespace scope on the CALLER's plugin lifecycle — the service
1161
- * proxy binds `this.ctx` to the caller at call time, so the scope's disposer
1162
- * belongs to the calling fiber. The scope derives from the shared mirror
1163
- * (whose invalidation subscriptions live with the providing plugin), so
1164
- * binding adds no wire read of its own and activation never blocks on the
1165
- * settings transport.
1166
- * @param spec - domain-owned namespace contract.
1167
- * @returns the bound scope consumed by the domain's services and rows.
1318
+ * Keep a registration alive while the Host serves any of some namespaces:
1319
+ * `register` runs once one of them is in the describe mirror, and its
1320
+ * disposer runs when none is or when the returned disposer runs. A plugin
1321
+ * whose page edits a namespace another plugin owns registers the page
1322
+ * through this, so a deployment that never composed the owner shows no
1323
+ * trace of the page. The caller owns the returned disposer and wraps it in
1324
+ * `ctx.effect`; unlike {@link bind}, nothing is registered on the caller's
1325
+ * context here.
1326
+ * @param namespaces - the settings namespaces the registration follows.
1327
+ * @param register - registers the contribution, given every namespace the Host serves; returns its disposer.
1328
+ * @returns the disposer ending the watch and any live registration.
1168
1329
  */
1169
- bind(spec) {
1170
- const ctx = this.ctx;
1171
- const controller = new SettingsScopeController(this.owner, spec, this.mirror, this.persistence, this.schema);
1172
- ctx.effect(() => {
1173
- this.mirror.ensure();
1174
- return async () => {
1175
- await controller.dispose();
1176
- };
1177
- }, `ui-settings: ${spec.namespace} settings scope`);
1178
- return controller;
1330
+ whileServed(namespaces, register) {
1331
+ let off;
1332
+ const sync = () => {
1333
+ const served = new Set(this.mirror.getSnapshot().view?.namespaces.map((view) => view.ns) ?? []);
1334
+ const watched = namespaces.some((namespace) => served.has(namespace));
1335
+ if (watched && off === void 0) off = register(served);
1336
+ else if (!watched && off !== void 0) {
1337
+ off();
1338
+ off = void 0;
1339
+ }
1340
+ };
1341
+ const unsubscribe = this.mirror.subscribe(sync);
1342
+ this.mirror.ensure();
1343
+ sync();
1344
+ return () => {
1345
+ unsubscribe();
1346
+ off?.();
1347
+ off = void 0;
1348
+ };
1179
1349
  }
1180
1350
  };
1181
1351
  //#endregion
@@ -1183,8 +1353,8 @@ window.__ModuleLoader__.load({
1183
1353
  /**
1184
1354
  * Client mirror of the Host settings document: the one `settings.describe`
1185
1355
  * reader in the browser. Every settings consumer derives from this store —
1186
- * per-namespace scopes through `SettingsScopeBinder.bind`, cross-namespace
1187
- * surfaces through the binder's shared describe face — so startup cost and
1356
+ * shared entry forms through `ConfigForms.get`, cross-namespace
1357
+ * surfaces through the provider's shared describe face — so startup cost and
1188
1358
  * freshness are properties of this class, not of how many features own a
1189
1359
  * preference. The Host stays the fact source: the mirror re-reads on the
1190
1360
  * invalidations its owning plugin subscribes to and folds write answers in
@@ -1331,14 +1501,8 @@ window.__ModuleLoader__.load({
1331
1501
  * forwarded settings invalidation it refreshes on.
1332
1502
  */
1333
1503
  const inject = ["remote", "remote.settings"];
1334
- /**
1335
- * Provide the settings-namespace scope service over one shared describe
1336
- * mirror, and keep that mirror fresh on the two signals that can move the
1337
- * settings document: a document commit and a (re)connect.
1338
- *
1339
- * Constructing the service in this plugin's fiber keeps its traced methods
1340
- * bound to each consuming plugin's context.
1341
- * @param ctx - client root context.
1504
+ /** Provide shared forms and refresh them on document changes and reconnects.
1505
+ * @param ctx Client provider context.
1342
1506
  */
1343
1507
  function apply(ctx) {
1344
1508
  const schema = new SettingsSchemaService(ctx);
@@ -1355,7 +1519,7 @@ window.__ModuleLoader__.load({
1355
1519
  for (const dispose of disposers) dispose();
1356
1520
  };
1357
1521
  }, "ui-settings: describe mirror invalidations");
1358
- new SettingsScopeBinder(ctx, {
1522
+ new ConfigForms(ctx, {
1359
1523
  mirror,
1360
1524
  schema,
1361
1525
  persistence