@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/README.i18n.yaml +2 -2
- package/README.md +20 -11
- package/README.zh.md +20 -11
- package/lib/client.js +281 -117
- package/lib/index.js +18 -4
- package/lib/types/client/{settings-contract.d.ts → config-form-types.d.ts} +16 -27
- package/lib/types/client/{settings-scope.d.ts → config-form.d.ts} +55 -37
- package/lib/types/client/contract/slots.d.ts +18 -1
- package/lib/types/client/developer-tools.d.ts +22 -0
- package/lib/types/client/index.d.ts +6 -24
- package/lib/types/client/settings-mirror.d.ts +2 -2
- package/lib/types/developer-tools-settings.d.ts +20 -0
- package/lib/types/index.d.ts +17 -3
- package/package.json +13 -11
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/
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
205
|
+
Time.setTimezoneOffset = setTimezoneOffset;
|
|
150
206
|
function getTimezoneOffset() {
|
|
151
207
|
return timezoneOffset;
|
|
152
208
|
}
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
+
Time.format = format;
|
|
197
253
|
function toDigits(source, length = 2) {
|
|
198
254
|
return source.toString().padStart(length, "0");
|
|
199
255
|
}
|
|
200
|
-
|
|
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
|
-
|
|
260
|
+
Time.template = template;
|
|
205
261
|
})(Time || (Time = {}));
|
|
206
262
|
//#endregion
|
|
207
|
-
//#region ../../../vendor/schemastery/
|
|
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/
|
|
936
|
-
/**
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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, "
|
|
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
|
|
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
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
* @
|
|
1167
|
-
*
|
|
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
|
-
|
|
1170
|
-
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
-
*
|
|
1187
|
-
* surfaces through the
|
|
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
|
-
*
|
|
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
|
|
1522
|
+
new ConfigForms(ctx, {
|
|
1359
1523
|
mirror,
|
|
1360
1524
|
schema,
|
|
1361
1525
|
persistence
|