@lmzhen/dsh-evolution-settings-ui 0.6.1 → 0.8.0

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.
@@ -4,13 +4,27 @@
4
4
  * Structural local types on purpose: this package is distributed outside the
5
5
  * platform repository and a value import of another plugin is forbidden, so the
6
6
  * settings scope and its snapshot are declared by the shape the renderer hands
7
- * over (`getSnapshot`/`subscribe`, status/value/user/writable).
7
+ * over (\`getSnapshot\`/\`subscribe\`, status/value/user/writable).
8
+ *
9
+ * Layout and behaviour follow the platform's own settings fields: a collapsed card
10
+ * header (namespace title, change count, chevron), then one block per field —
11
+ * label with its unit and source chip, a control HOLDING THE CURRENT VALUE, the
12
+ * Chinese hint — and one 放弃修改/保存 pair at the card's foot. The control is
13
+ * typed from the registry (\`control\`), so booleans are switches, enums are
14
+ * selects and numbers are numeric inputs with the unit shown beside the label.
8
15
  */
9
16
  import { type ReactNode } from 'react';
10
17
  import type { ClientParamField } from './generated-params.ts';
11
- import type { MessageKey } from './messages.ts';
18
+ import { type MessageKey } from './messages.ts';
12
19
  import type { ParamSectionSnapshot, ParamSectionSource } from './seam.ts';
13
- /** Injected face: plain data and callbacks, plus the hook seat. */
20
+ /**
21
+ * Injected face: plain data and callbacks, plus the hook seat.
22
+ *
23
+ * The `hooks` compartment belongs to the shell, not to the component: the
24
+ * renderer binds its entries to `use<Name>` props and omits `hooks` from what
25
+ * the component receives. A card that reads `props.hooks` therefore reads a prop
26
+ * that never exists.
27
+ */
14
28
  export interface ParamCardFace {
15
29
  namespace: string;
16
30
  fields: readonly ClientParamField[];
@@ -21,9 +35,13 @@ export interface ParamCardFace {
21
35
  paramSection: ParamSectionSource;
22
36
  };
23
37
  }
24
- /** Props the renderer binds for one card: the inject face plus its hook seat. */
25
- export type ParamCardProps = ParamCardFace & {
26
- /** Bound from `hooks.paramSection` by the renderer. */
38
+ /**
39
+ * Props the renderer binds for one card: the inject face minus its hook compartment,
40
+ * plus the seats bound from it. Spelled the way the shell derives it, so reaching for
41
+ * `props.hooks` fails the type check instead of failing at runtime.
42
+ */
43
+ export type ParamCardProps = Omit<ParamCardFace, 'hooks'> & {
44
+ /** Bound from \`hooks.paramSection\` by the renderer. */
27
45
  readonly useParamSection: <T>(selector: (state: ParamSectionSnapshot) => T) => T;
28
46
  };
29
47
  /**
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The section shell: title, one-line explanation, then one card per namespace.
3
+ *
4
+ * This is the shape the platform's own settings sections use — a heading plus the
5
+ * child slots it declared in its registration. The cards themselves are separate
6
+ * registrations keyed by namespace, so a namespace the Host does not serve simply
7
+ * renders nothing and a deployment that drops this row loses the whole section.
8
+ * @module @lmzhen/dsh-evolution-settings-ui/client
9
+ */
10
+ import { type ReactNode } from 'react';
11
+ import { type SectionFace } from './seam.ts';
12
+ /**
13
+ * Render the section.
14
+ * @param face - the shell's inject face: translator and child-slot renderer.
15
+ * @returns the section element.
16
+ */
17
+ export declare function SettingsSection(face: SectionFace): ReactNode;
18
+ //# sourceMappingURL=SettingsSection.d.ts.map
@@ -8,8 +8,18 @@
8
8
  export interface ClientParamField {
9
9
  id: string;
10
10
  group: string;
11
- /** The registry summary, used as the field help text. */
11
+ /** The registry summary (English), kept for parity with PARAMETERS.md. */
12
12
  doc: string;
13
+ /** Card label, from the registry UI tail (Chinese). */
14
+ label: string;
15
+ /** Card help text, from the registry UI tail (Chinese). */
16
+ hint: string;
17
+ /** Which control the card renders. */
18
+ control: 'number' | 'switch' | 'select' | 'text';
19
+ /** Unit suffix for a number field ('' when it carries none). */
20
+ unit: string;
21
+ /** Allowed values for a select field ([] otherwise). */
22
+ values: readonly string[];
13
23
  }
14
24
  /** One card: the settings namespace plus the fields it exposes. */
15
25
  export interface ClientParamSection {
@@ -1,18 +1,28 @@
1
1
  /**
2
- * Browser half: one card per settings namespace the family registers.
2
+ * Browser half: one settings section ("自进化") hosting one card per namespace.
3
3
  *
4
- * The join key is the namespace itself — the Host half of each owning plugin
5
- * registers it, this half claims it in the keyed `settings.plugin.item` slot, and
6
- * the configuration tab pairs them. A namespace the Host does not serve renders
7
- * nothing, and a deployment that drops this row shows no cards at all: the pair
8
- * carries no Host-side behaviour.
4
+ * Until 0.7.0 the cards lived inside the platform's own 插件 section, because
5
+ * this bundle injected `@deepseek-ai/dsh-client-ui-settings-plugins` and claimed
6
+ * its keyed `settings.plugin.item` slot. The parameters are not a plugin
7
+ * inventory though — the other feature areas (market, skins, Web plugins, side
8
+ * cards) each register their OWN section, so this bundle now does the same:
9
+ * inject the settings shell, register `settings.section`, and host the cards in
10
+ * a keyed child slot of that section.
11
+ *
12
+ * The join key is still the namespace itself: the Host half of each owning plugin
13
+ * registers it, this half claims it in the keyed card slot, and the section pairs
14
+ * them. A namespace the Host does not serve renders nothing, and a deployment that
15
+ * drops this row shows no section at all: the pair carries no Host-side behaviour.
9
16
  * @module @lmzhen/dsh-evolution-settings-ui/client
10
17
  */
11
18
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
12
- /** Required client services: the slot registry and the settings transport. */
19
+ /** Required client services: the slot registry, the settings transport, the locale seat. */
13
20
  export declare const inject: string[];
21
+ /** Section id and nav order (the platform's own sections sit at 0/10/15/20). */
22
+ export declare const SECTION_ID = "evolution";
23
+ export declare const SECTION_ORDER = 25;
14
24
  /**
15
- * Register one card per generated namespace section.
25
+ * Register the locale namespace, the section and its cards.
16
26
  * @param ctx - client cordis context.
17
27
  */
18
28
  export declare function apply(ctx: ClientContext): void;
@@ -1,29 +1,52 @@
1
1
  /**
2
- * Copy for the evolution parameter cards.
2
+ * Copy for the evolution settings section and its parameter cards.
3
3
  *
4
- * The bundle carries its own dictionary: this package is distributed outside the
5
- * platform repository, whose typed locale seats are declared per in-repo
6
- * namespace. Components never inline a string — the card receives `t` through its
7
- * inject face, which is where a locale-service binding would plug in later.
4
+ * The section is registered through the platform's locale seat (`ctx.locale`),
5
+ * which is the same route the other out-of-repo client plugins take. The earlier
6
+ * note in this package's README — that an outside package cannot bind the locale
7
+ * seat — is wrong: `locale.register(namespace, { zh, en })` is the documented
8
+ * call, and it is what makes the section title and its copy follow the UI
9
+ * language instead of shipping one hard-coded dictionary.
8
10
  */
9
- /** Message keys the cards render. */
10
- export declare const MESSAGES: {
11
- readonly title: "Evolution parameters";
12
- readonly overridden: "user override";
13
- readonly deployment: "deployment value";
14
- readonly loading: "reading the settings document…";
15
- readonly unavailable: "this namespace is not served by the Host (its owning plugin row is not mounted)";
16
- readonly readonly: "the settings document is read-only in this session (memory mode)";
17
- readonly apply: "Apply";
18
- readonly reset: "Reset to deployment";
19
- readonly empty: "no writable field in this namespace";
11
+ /** Locale namespace this bundle registers. */
12
+ export declare const NS = "evolution-settings";
13
+ /** Chinese copy (the family's primary language). */
14
+ export declare const zh: {
15
+ readonly title: "自进化";
16
+ readonly subtitle: "审查/记忆/策展/技能四个命名空间的参数可在用户层覆盖;此处只列可改项,其余仍由部署面决定。";
17
+ readonly overridden: "用户";
18
+ readonly deployment: "部署";
19
+ readonly loading: "正在读取设置文档…";
20
+ readonly unavailable: "这个命名空间没有宿主提供服务(对应插件行未挂载)";
21
+ readonly readonly: "本次会话的设置文档是只读的(内存模式)";
22
+ readonly apply: "保存";
23
+ readonly reset: "恢复部署默认";
24
+ readonly empty: "这个命名空间没有可改项";
25
+ readonly changed: "{n} 项已改";
26
+ readonly discard: "放弃修改";
27
+ readonly saving: "保存中…";
28
+ readonly refused: "写入未被接受——宿主的校验拒绝了它(值保持原样)。原因见 /evolution params 或 /evolution doctor。";
29
+ readonly cardReview: "审查";
30
+ readonly cardMemory: "记忆";
31
+ readonly cardToolMemory: "记忆工具";
32
+ readonly cardCurator: "策展";
33
+ readonly cardSkills: "技能";
20
34
  };
35
+ /** English copy, key for key with {@link zh}. */
36
+ export declare const en: Record<keyof typeof zh, string>;
37
+ /**
38
+ * Card titles per settings namespace. A namespace without an entry falls back to
39
+ * its raw name, so a Host that registers a new namespace still renders — the
40
+ * title is presentation, the namespace is the join key.
41
+ */
42
+ export declare const NAMESPACE_TITLES: Readonly<Record<string, string>>;
21
43
  /** One message key. */
22
- export type MessageKey = keyof typeof MESSAGES;
44
+ export type MessageKey = keyof typeof zh;
23
45
  /**
24
- * Resolve one message.
46
+ * Resolve one message without the locale seat (tests and the fallback path).
25
47
  * @param key - the message key.
48
+ * @param language - which dictionary to read; defaults to Chinese.
26
49
  * @returns the copy this bundle carries.
27
50
  */
28
- export declare function message(key: MessageKey): string;
51
+ export declare function message(key: MessageKey, language?: 'zh' | 'en'): string;
29
52
  //# sourceMappingURL=messages.d.ts.map
@@ -4,11 +4,15 @@
4
4
  * The package is distributed OUTSIDE the platform repository, so it declares the
5
5
  * two seams it consumes instead of importing another plugin's values (forbidden
6
6
  * by the client bundle-purity rule) or its types (which would drag the platform
7
- * sources into this project). The shapes are the renderer's contract: a keyed
8
- * `settings.plugin.item` registration, the inject face it hands back, and the
9
- * per-namespace settings scope the write path fences with the revision it read.
7
+ * sources into this project). The shapes are the shell's contract: a
8
+ * `settings.section` registration hosting a keyed card slot, the inject faces
9
+ * both hand back, the per-namespace settings scope the write path fences with the
10
+ * revision it read, and the locale seat the section's copy resolves through.
10
11
  * @module @lmzhen/dsh-evolution-settings-ui/client
11
12
  */
13
+ import type { ReactNode } from 'react';
14
+ /** Our card slot, hosted by the section this bundle registers. */
15
+ export declare const CARD_SLOT = "evolution.namespace.card";
12
16
  /** Snapshot of one settings namespace, as the client scope reports it. */
13
17
  export interface ParamSectionSnapshot {
14
18
  status: 'loading' | 'ready' | 'unavailable';
@@ -27,23 +31,58 @@ export interface SettingsScopeLike {
27
31
  set(field: string, value: unknown): Promise<void>;
28
32
  unset(field: string): Promise<void>;
29
33
  }
30
- /** Registration options the keyed slot takes. */
34
+ /** Registration options the keyed card slot under our own section takes. */
31
35
  export interface SlotRegisterOptions {
32
- name: 'settings.plugin.item';
36
+ name: typeof CARD_SLOT;
33
37
  /** The settings namespace this card edits — the slot's key. */
34
38
  key: string;
35
39
  inject: () => unknown;
36
40
  }
41
+ /** Options a `settings.section` registration carries (the shell's own shape). */
42
+ export interface SectionRegisterOptions {
43
+ name: 'settings.section';
44
+ /** Section id; the shell derives the nav entry's DOM id from it. */
45
+ id: string;
46
+ /** Nav order: the platform's own sections sit at 0/10/15/20. */
47
+ order: number;
48
+ /** Lazy label: the shell re-reads it when the locale changes. */
49
+ label: () => string;
50
+ /** Locale namespace the label and the section's copy resolve through. */
51
+ locale: string;
52
+ /** Child slots this section hosts, declared for the shell's renderer. */
53
+ children?: Record<string, {
54
+ kind: 'keyed' | 'list' | 'single';
55
+ scope: 'root';
56
+ }>;
57
+ }
58
+ /**
59
+ * The face the shell hands a section component: a translator bound to our locale
60
+ * namespace and the renderer for the child slots we declared.
61
+ */
62
+ export interface SectionFace {
63
+ t: (key: string) => string;
64
+ /** Render one child slot; `filter` selects keyed entries (e.g. `{ entryKey }`). */
65
+ renderSlot: (name: string, props?: Record<string, unknown>, filter?: Record<string, unknown>) => ReactNode;
66
+ }
67
+ /** The locale seat: register this bundle's namespace, bind a translator. */
68
+ export interface LocaleSeat {
69
+ register(namespace: string, dictionaries: {
70
+ zh: Record<string, string>;
71
+ en: Record<string, string>;
72
+ }): () => void;
73
+ bind(namespace: string): (key: string) => string;
74
+ }
37
75
  /** The slice of the client context this plugin consumes. */
38
76
  export interface ClientSeam {
39
77
  slots: {
40
78
  inject(name: string, callback: () => Generator<unknown, void, unknown>): unknown;
41
- register(options: SlotRegisterOptions, component: (props: never) => unknown): () => void;
79
+ register(options: SlotRegisterOptions | SectionRegisterOptions, component: (props: never) => unknown): () => void;
42
80
  };
43
81
  settingsScope: {
44
82
  bind(spec: {
45
83
  namespace: string;
46
84
  }): SettingsScopeLike;
47
85
  };
86
+ locale: LocaleSeat;
48
87
  }
49
88
  //# sourceMappingURL=seam.d.ts.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The save verdict: did the Host take the values this card asked for?
3
+ *
4
+ * A write promise that resolves is not a verdict. Both settings scopes this bundle runs
5
+ * on finish a recovery read before it resolves, so a value the Host refused is simply
6
+ * still the previous one. Presence in the user layer cannot tell refusal from acceptance
7
+ * on a field the operator had already overridden — the refused value's key is present
8
+ * too — so the verdict compares what each staged id holds now against what was requested.
9
+ * @module @lmzhen/dsh-evolution-settings-ui
10
+ */
11
+ /** One staged write awaiting its verdict. */
12
+ export interface PendingWrite {
13
+ /** Parameter id the write named. */
14
+ readonly id: string;
15
+ /** The value handed to the scope's `set`. */
16
+ readonly want: unknown;
17
+ }
18
+ /**
19
+ * Whether every staged write is visible in the user layer.
20
+ * @param pending - the writes awaiting a verdict.
21
+ * @param user - the user layer of the post-write snapshot; a non-section reads as empty.
22
+ * @returns true when each staged id now holds the value that was requested.
23
+ */
24
+ export declare function landedWrites(pending: readonly PendingWrite[], user: unknown): boolean;
25
+ //# sourceMappingURL=settle.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The section's stylesheet.
3
+ *
4
+ * The platform's own client bundles compile CSS Modules into a string and inject
5
+ * it once behind a \`<style data-plugin-css="<pkg>/<file>">\` tag; this bundle is
6
+ * built outside that pipeline, so it carries its own string and injects it the
7
+ * same way. Every rule reads the platform's design tokens (\`--dsw-alias-*\`), so
8
+ * the section follows the light and dark themes without owning a colour, and the
9
+ * field metrics (12px/6px padding-gap, 13px label, 12px hint) match the ones the
10
+ * platform's own settings fields use.
11
+ * @module @lmzhen/dsh-evolution-settings-ui/client
12
+ */
13
+ /** Tag id that makes the injection idempotent. */
14
+ export declare const CSS_TAG_ID = "@lmzhen/dsh-evolution-settings-ui/settings.css";
15
+ /** The stylesheet the section injects once. */
16
+ export declare const CSS: string;
17
+ /** The slice of the DOM the injection touches (this package typechecks without DOM lib). */
18
+ interface StyleHost {
19
+ querySelector(selector: string): unknown;
20
+ createElement(tag: string): {
21
+ dataset: Record<string, string>;
22
+ textContent: string;
23
+ };
24
+ head: {
25
+ appendChild(node: unknown): void;
26
+ };
27
+ }
28
+ /**
29
+ * Inject the stylesheet once per document.
30
+ * @param host - the document to inject into; defaults to the global one.
31
+ */
32
+ export declare function injectStyles(host?: StyleHost): void;
33
+ export {};
34
+ //# sourceMappingURL=styles.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-settings-ui",
3
3
  "description": "Web settings cards for the evolution family's parameter namespaces (community build)",
4
- "version": "0.6.1",
4
+ "version": "0.8.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -33,7 +33,8 @@
33
33
  "client": {
34
34
  "platform": "web",
35
35
  "inject": [
36
- "@deepseek-ai/dsh-client-ui-settings-plugins"
36
+ "@deepseek-ai/dsh-client-ui-settings",
37
+ "@deepseek-ai/dsh-client-locale"
37
38
  ]
38
39
  }
39
40
  },