@qilin-agent/client-ui-settings 3.1.4 → 3.1.6

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 CHANGED
@@ -39,8 +39,8 @@
39
39
  en: ffd6c51916031c1d
40
40
  zh: 703566b0885fd19e
41
41
  /qilin-agent-client-ui-settings/understand-the-implementation/schema-service:
42
- en: a9247e83278c5d40
43
- zh: 051d24681036639d
42
+ en: aa86a5f5e64362f3
43
+ zh: 37e9a4da01cf0cf0
44
44
  /qilin-agent-client-ui-settings/further-exploration:
45
45
  en: 2ce4e8d3a90e61ce
46
46
  zh: 9b5510de7f4b320d
package/README.md CHANGED
@@ -65,7 +65,7 @@ The plugin injects `remote` with its `settings` namespace, resolves Host persist
65
65
 
66
66
  ### Schema service
67
67
 
68
- `ctx.settingsSchema` performs synchronous schema rehydration, validation, and immutable path editing for settings plugins. A section that is not a plain object, fails its rehydrated schema, or carries a schema envelope this client cannot rehydrate publishes no value at all.
68
+ `ctx.settingsSchema` performs synchronous schema rehydration, validation, and immutable path editing for settings plugins; it delegates to [`@qilin-agent/client-schema-form`](../schema-form/README.md), the shared model layer that shipped editors import directly. A section that is not a plain object, fails its rehydrated schema, or carries a schema envelope this client cannot rehydrate publishes no value at all.
69
69
 
70
70
  </details>
71
71
 
package/README.zh.md CHANGED
@@ -65,7 +65,7 @@ kind: "package-reference"
65
65
 
66
66
  ### Schema 服务
67
67
 
68
- `ctx.settingsSchema` 为设置插件执行同步 schema 重建、校验与不可变路径编辑。分区不是普通对象、未通过其重建后的 schema 校验、或携带本客户端无法重建的 schema 信封时,一律不发布任何值。
68
+ `ctx.settingsSchema` 为设置插件执行同步 schema 重建、校验与不可变路径编辑;它委派给 [`@qilin-agent/client-schema-form`](../schema-form/README.zh.md)——随包发布的编辑器直接导入的共享模型层。分区不是普通对象、未通过其重建后的 schema 校验、或携带本客户端无法重建的 schema 信封时,一律不发布任何值。
69
69
 
70
70
  </details>
71
71
 
package/lib/client.js CHANGED
@@ -897,19 +897,100 @@ window.__ModuleLoader__.load({
897
897
  "preserve"
898
898
  ], ({ inner }, isInner) => inner.toString(isInner));
899
899
  //#endregion
900
- //#region lib/types/client/schema.js
901
- /** Synchronous schema introspection and immutable settings-draft edits. */
900
+ //#region ../schema-form/src/model.ts
901
+ /**
902
+ * Schema introspection and draft-editing helpers behind settings editors.
903
+ * The serialized schemastery envelope (`schema.toJSON()`) rehydrates into a
904
+ * live validator whose node relations (`dict`/`inner`) editors probe for
905
+ * field presence and roles; drafts are edited immutably by path.
906
+ * @module @qilin-agent/client-schema-form/model
907
+ */
908
+ /**
909
+ * Rehydrate a serialized schema envelope into a live validator/node tree.
910
+ * @param serialized - `schema.toJSON()` output received over the wire.
911
+ * @returns the root schema node.
912
+ */
913
+ function rehydrateSchema(serialized) {
914
+ return new Schema(serialized);
915
+ }
916
+ /**
917
+ * Validate a draft against a rehydrated schema.
918
+ * @param schema - rehydrated root node.
919
+ * @param draft - candidate value.
920
+ * @returns the validation failure message, or `undefined` when the draft passes.
921
+ */
922
+ function validateDraft(schema, draft) {
923
+ try {
924
+ schema(draft);
925
+ return;
926
+ } catch (error) {
927
+ return error instanceof Error ? error.message : String(error);
928
+ }
929
+ }
930
+ /**
931
+ * Resolve the schema node at a settings path (the configurable-provider
932
+ * directory's `settingsPath` vocabulary): object properties by name, dict
933
+ * entries through `inner`. An unresolvable segment returns `undefined` so
934
+ * the caller falls back instead of rendering a wrong subtree.
935
+ * @param root - rehydrated section root node.
936
+ * @param path - key path from the section root.
937
+ * @returns the node describing that position, or `undefined`.
938
+ */
939
+ function nodeAtPath(root, path) {
940
+ let node = root;
941
+ for (const key of path) {
942
+ if (node === void 0) return void 0;
943
+ if (node.type === "object") node = node.dict?.[key];
944
+ else if (node.type === "dict" || node.type === "array") node = node.inner;
945
+ else return void 0;
946
+ }
947
+ return node;
948
+ }
949
+ /**
950
+ * Read a nested value by path.
951
+ * @param value - root value (draft or fallback layer).
952
+ * @param path - key path from the root; array indexes as strings.
953
+ * @returns the value at the path, or `undefined` along a missing branch.
954
+ */
955
+ function getPath(value, path) {
956
+ let current = value;
957
+ for (const key of path) {
958
+ if (Array.isArray(current)) {
959
+ current = current[Number(key)];
960
+ continue;
961
+ }
962
+ if (typeof current !== "object" || current === null) return void 0;
963
+ current = current[key];
964
+ }
965
+ return current;
966
+ }
967
+ /**
968
+ * Whether a draft explicitly carries the path (its presence marks a user
969
+ * override, independent of the value stored there).
970
+ * @param value - root value (draft or fallback layer).
971
+ * @param path - key path from the root; array indexes as strings.
972
+ * @returns whether the path's final key exists on its parent.
973
+ */
974
+ function hasPath(value, path) {
975
+ if (path.length === 0) return value !== void 0;
976
+ const parent = getPath(value, path.slice(0, -1));
977
+ const key = path[path.length - 1];
978
+ if (Array.isArray(parent)) return Number(key) < parent.length;
979
+ if (typeof parent !== "object" || parent === null) return false;
980
+ return key in parent;
981
+ }
902
982
  function cloneContainer(container, key) {
903
983
  if (Array.isArray(container)) return [...container];
904
984
  if (typeof container === "object" && container !== null) return { ...container };
905
985
  return /^\d+$/.test(key) ? [] : {};
906
986
  }
987
+ /** Clone the container spine down to the leaf's parent, materializing missing intermediates. */
907
988
  function cloneSpine(root, path) {
908
989
  const result = { ...root };
909
990
  let target = result;
910
- for (let index = 0; index < path.length - 1; index++) {
911
- const key = path[index];
912
- const child = cloneContainer(Array.isArray(target) ? target[Number(key)] : target[key], path[index + 1]);
991
+ for (let i = 0; i < path.length - 1; i++) {
992
+ const key = path[i];
993
+ const child = cloneContainer(Array.isArray(target) ? target[Number(key)] : target[key], path[i + 1]);
913
994
  if (Array.isArray(target)) target[Number(key)] = child;
914
995
  else target[key] = child;
915
996
  target = child;
@@ -921,8 +1002,45 @@ window.__ModuleLoader__.load({
921
1002
  };
922
1003
  }
923
1004
  /**
1005
+ * Immutably set a nested value, materializing missing intermediate containers.
1006
+ * @param root - draft root (never mutated).
1007
+ * @param path - non-empty key path.
1008
+ * @param value - value to store at the path.
1009
+ * @returns the new draft root.
1010
+ * @throws when `path` is empty.
1011
+ */
1012
+ function setPath(root, path, value) {
1013
+ if (path.length === 0) throw new Error("schema-form: setPath needs a non-empty path");
1014
+ const { result, parent, leaf } = cloneSpine(root, path);
1015
+ if (Array.isArray(parent)) parent[Number(leaf)] = value;
1016
+ else parent[leaf] = value;
1017
+ return result;
1018
+ }
1019
+ /**
1020
+ * Immutably remove a nested key (the per-field reset: the resolved value
1021
+ * falls back to the composition base and schema defaults). Removing along a
1022
+ * missing branch returns the root unchanged.
1023
+ * @param root - draft root (never mutated).
1024
+ * @param path - non-empty key path.
1025
+ * @returns the new draft root.
1026
+ * @throws when `path` is empty.
1027
+ */
1028
+ function deletePath(root, path) {
1029
+ if (path.length === 0) throw new Error("schema-form: deletePath needs a non-empty path");
1030
+ if (!hasPath(root, path)) return root;
1031
+ const { result, parent, leaf } = cloneSpine(root, path);
1032
+ if (Array.isArray(parent)) parent.splice(Number(leaf), 1);
1033
+ else Reflect.deleteProperty(parent, leaf);
1034
+ return result;
1035
+ }
1036
+ //#endregion
1037
+ //#region lib/types/client/schema.js
1038
+ /** Synchronous schema introspection and immutable settings-draft edits. */
1039
+ /**
924
1040
  * Settings-owned synchronous schema service. Dynamic client plugins receive
925
- * this Cordis entity instead of importing executable helpers from one another.
1041
+ * this Cordis entity instead of importing executable helpers from one another;
1042
+ * it delegates to the shared schema/draft model layer so one implementation
1043
+ * backs both this service and the shipped editors that import it directly.
926
1044
  */
927
1045
  var SettingsSchemaService = class extends _qilin_agent_kylin.Service {
928
1046
  /** @param ctx - providing ui-settings context. */
@@ -935,7 +1053,7 @@ window.__ModuleLoader__.load({
935
1053
  * @returns live schema node.
936
1054
  */
937
1055
  rehydrate(serialized) {
938
- return new Schema(serialized);
1056
+ return rehydrateSchema(serialized);
939
1057
  }
940
1058
  /**
941
1059
  * Validate a settings draft.
@@ -944,12 +1062,7 @@ window.__ModuleLoader__.load({
944
1062
  * @returns validation failure text, or `undefined` when valid.
945
1063
  */
946
1064
  validate(schema, draft) {
947
- try {
948
- schema(draft);
949
- return;
950
- } catch (error) {
951
- return error instanceof Error ? error.message : String(error);
952
- }
1065
+ return validateDraft(schema, draft);
953
1066
  }
954
1067
  /**
955
1068
  * Resolve an object, dict, or array schema node at a settings path.
@@ -958,14 +1071,7 @@ window.__ModuleLoader__.load({
958
1071
  * @returns the resolved node, or `undefined` when the path is absent.
959
1072
  */
960
1073
  nodeAtPath(root, path) {
961
- let node = root;
962
- for (const key of path) {
963
- if (node === void 0) return void 0;
964
- if (node.type === "object") node = node.dict?.[key];
965
- else if (node.type === "dict" || node.type === "array") node = node.inner;
966
- else return void 0;
967
- }
968
- return node;
1074
+ return nodeAtPath(root, path);
969
1075
  }
970
1076
  /**
971
1077
  * Read a nested value by a string-key or array-index path.
@@ -974,16 +1080,7 @@ window.__ModuleLoader__.load({
974
1080
  * @returns the resolved value, or `undefined` when the path is absent.
975
1081
  */
976
1082
  getPath(value, path) {
977
- let current = value;
978
- for (const key of path) {
979
- if (Array.isArray(current)) {
980
- current = current[Number(key)];
981
- continue;
982
- }
983
- if (typeof current !== "object" || current === null) return void 0;
984
- current = current[key];
985
- }
986
- return current;
1083
+ return getPath(value, path);
987
1084
  }
988
1085
  /**
989
1086
  * Report whether the final path key exists independently of its value.
@@ -992,12 +1089,7 @@ window.__ModuleLoader__.load({
992
1089
  * @returns whether the path exists.
993
1090
  */
994
1091
  hasPath(value, path) {
995
- if (path.length === 0) return value !== void 0;
996
- const parent = this.getPath(value, path.slice(0, -1));
997
- const key = path[path.length - 1];
998
- if (Array.isArray(parent)) return Number(key) < parent.length;
999
- if (typeof parent !== "object" || parent === null) return false;
1000
- return key in parent;
1092
+ return hasPath(value, path);
1001
1093
  }
1002
1094
  /**
1003
1095
  * Immutably set a nested value, materializing missing containers.
@@ -1008,11 +1100,7 @@ window.__ModuleLoader__.load({
1008
1100
  * @throws when `path` is empty.
1009
1101
  */
1010
1102
  setPath(root, path, value) {
1011
- if (path.length === 0) throw new Error("ui-settings: setPath needs a non-empty path");
1012
- const { result, parent, leaf } = cloneSpine(root, path);
1013
- if (Array.isArray(parent)) parent[Number(leaf)] = value;
1014
- else parent[leaf] = value;
1015
- return result;
1103
+ return setPath(root, path, value);
1016
1104
  }
1017
1105
  /**
1018
1106
  * Immutably remove a nested key, preserving an unchanged missing root.
@@ -1022,12 +1110,7 @@ window.__ModuleLoader__.load({
1022
1110
  * @throws when `path` is empty.
1023
1111
  */
1024
1112
  deletePath(root, path) {
1025
- if (path.length === 0) throw new Error("ui-settings: deletePath needs a non-empty path");
1026
- if (!this.hasPath(root, path)) return root;
1027
- const { result, parent, leaf } = cloneSpine(root, path);
1028
- if (Array.isArray(parent)) parent.splice(Number(leaf), 1);
1029
- else Reflect.deleteProperty(parent, leaf);
1030
- return result;
1113
+ return deletePath(root, path);
1031
1114
  }
1032
1115
  };
1033
1116
  //#endregion
package/lib/index.js CHANGED
@@ -5,16 +5,18 @@ import z from "@qilin-agent/schemastery";
5
5
  const DEVELOPER_TOOLS_NAMESPACE = "ui-settings";
6
6
  /** New installations and missing values enable the full interface. */
7
7
  const DeveloperToolsSettingsFields = { enabled: z.boolean().default(true) };
8
- /** Durable developer-tools schema; also the wire envelope the browser scope validates against. */
9
- const DeveloperToolsSettingsSchema = z.object({ enabled: DeveloperToolsSettingsFields["enabled"] });
8
+ z.object({ enabled: DeveloperToolsSettingsFields["enabled"] });
10
9
  //#endregion
11
10
  //#region lib/types/index.js
12
- /** Host registration for the shared developer-tool preference. */
13
- /** Register the durable developer-tools section when a provider exists. */
11
+ /** Live preferences projected to the browser. */
12
+ const Config = z.object({ enabled: DeveloperToolsSettingsFields["enabled"].volatile() });
13
+ /** Host preferences are consumed through the configuration form projection.
14
+ * @param ctx Plugin context used for optional settings presentation.
15
+ */
14
16
  function apply(ctx) {
15
- ctx.inject(["settings"], (settingsCtx) => {
16
- settingsCtx.settings.register(DEVELOPER_TOOLS_NAMESPACE, DeveloperToolsSettingsSchema);
17
+ ctx.inject(["settings"], (child) => {
18
+ child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
17
19
  });
18
20
  }
19
21
  //#endregion
20
- export { DEVELOPER_TOOLS_NAMESPACE, apply };
22
+ export { Config, DEVELOPER_TOOLS_NAMESPACE, apply };
@@ -1,12 +1,13 @@
1
1
  /** Synchronous schema introspection and immutable settings-draft edits. */
2
2
  import { Service } from '@qilin-agent/kylin';
3
3
  import type { Context } from '@qilin-agent/kylin';
4
- import Schema from '@qilin-agent/schemastery';
5
- /** Live schemastery node used for settings introspection and validation. */
6
- export type SchemaNode = Schema;
4
+ import { type SchemaNode } from '@qilin-agent/client-schema-form';
5
+ export type { SchemaNode };
7
6
  /**
8
7
  * Settings-owned synchronous schema service. Dynamic client plugins receive
9
- * this Cordis entity instead of importing executable helpers from one another.
8
+ * this Cordis entity instead of importing executable helpers from one another;
9
+ * it delegates to the shared schema/draft model layer so one implementation
10
+ * backs both this service and the shipped editors that import it directly.
10
11
  */
11
12
  export declare class SettingsSchemaService extends Service {
12
13
  /** @param ctx - providing ui-settings context. */
@@ -1,6 +1,19 @@
1
- /** Host registration for the shared developer-tool preference. */
2
- import type { Context } from '@qilin-agent/kylin';
1
+ import type { Volatile, Context } from '@qilin-agent/kylin';
2
+ import z from '@qilin-agent/schemastery';
3
3
  export { DEVELOPER_TOOLS_NAMESPACE, type DeveloperToolsSettings } from './developer-tools-settings.ts';
4
- /** Register the durable developer-tools section when a provider exists. */
4
+ /** Runtime preferences projected to the browser. */
5
+ export interface Config {
6
+ /** Whether developer tools are enabled. */
7
+ enabled: Volatile<boolean>;
8
+ }
9
+ /** Live preferences projected to the browser. */
10
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
11
+ enabled: z<boolean, boolean, "volatile-defined">;
12
+ }>>, Schemastery.ObjectT<NoInfer<{
13
+ enabled: z<boolean, boolean, "volatile-defined">;
14
+ }>>, "plain">;
15
+ /** Host preferences are consumed through the configuration form projection.
16
+ * @param ctx Plugin context used for optional settings presentation.
17
+ */
5
18
  export declare function apply(ctx: Context): void;
6
19
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qilin-agent/client-ui-settings",
3
3
  "description": "Settings domain base plugin: the settings-namespace scope service and the canonical settings slot-type contract",
4
- "version": "3.1.4",
4
+ "version": "3.1.6",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -38,18 +38,19 @@
38
38
  },
39
39
  "license": "MIT",
40
40
  "peerDependencies": {
41
- "@qilin-agent/kylin": "~4.0.3"
41
+ "@qilin-agent/kylin": "~4.0.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "~18.3.1",
45
45
  "react": "^18.2.0",
46
- "@qilin-agent/api-remotes": "3.1.4",
47
- "@qilin-agent/client-store": "3.1.4",
48
- "@qilin-agent/client-test-runtime": "3.1.4",
49
- "@qilin-agent/kylin": "~4.0.3",
50
- "@qilin-agent/client-ui-slots": "3.1.4",
51
- "@qilin-agent/settings": "3.1.4",
52
- "@qilin-agent/util-values": "3.1.4"
46
+ "@qilin-agent/api-remotes": "3.1.6",
47
+ "@qilin-agent/client-schema-form": "3.1.6",
48
+ "@qilin-agent/kylin": "~4.0.5",
49
+ "@qilin-agent/client-test-runtime": "3.1.6",
50
+ "@qilin-agent/client-store": "3.1.6",
51
+ "@qilin-agent/client-ui-slots": "3.1.6",
52
+ "@qilin-agent/settings": "3.1.6",
53
+ "@qilin-agent/util-values": "3.1.6"
53
54
  },
54
55
  "files": [
55
56
  "lib/index.js",
@@ -57,7 +58,7 @@
57
58
  "lib/types/**/*.d.ts"
58
59
  ],
59
60
  "dependencies": {
60
- "@qilin-agent/schemastery": "~3.18.3"
61
+ "@qilin-agent/schemastery": "~3.18.5"
61
62
  },
62
63
  "scripts": {
63
64
  "bundle": "tsdown",