@changfenhuang/dsh-genui 0.9.8 → 0.9.9

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +13 -8
  3. package/README.zh-CN.md +13 -8
  4. package/SKILL.md +5 -5
  5. package/lib/client.js +3 -3
  6. package/lib/index.js +1273 -338
  7. package/lib/types/client/blocks/advanced.d.ts +1 -1
  8. package/lib/types/client/blocks/checkbox.d.ts +17 -0
  9. package/lib/types/client/blocks/forms.d.ts +3 -8
  10. package/lib/types/client/blocks/state.d.ts +18 -14
  11. package/lib/types/client/dom-fence.d.ts +1 -1
  12. package/lib/types/client/genui-runtime/diagnostics.d.ts +18 -0
  13. package/lib/types/client/genui-runtime/index.d.ts +1 -0
  14. package/lib/types/client/genui-runtime/limits.d.ts +54 -0
  15. package/lib/types/client/genui-runtime/normalize.d.ts +15 -0
  16. package/lib/types/client/genui-runtime/schema.d.ts +73 -0
  17. package/lib/types/client/genui-runtime/value-utils.d.ts +18 -0
  18. package/lib/types/client/guard.d.ts +60 -71
  19. package/lib/types/client/interaction-store.d.ts +7 -2
  20. package/lib/types/client/panel-command.d.ts +1 -1
  21. package/lib/types/client/primitive-labels.d.ts +11 -0
  22. package/lib/types/client/spec.d.ts +17 -17
  23. package/lib/types/plugin/chart-contract.d.ts +6 -4
  24. package/lib/types/plugin/index.d.ts +1 -4
  25. package/package.json +74 -13
  26. package/src/client/GenuiBlock.tsx +27 -11
  27. package/src/client/blocks/advanced.tsx +6 -5
  28. package/src/client/blocks/charts.tsx +1 -1
  29. package/src/client/blocks/checkbox.tsx +60 -0
  30. package/src/client/blocks/diagram/index.tsx +1 -1
  31. package/src/client/blocks/forms.tsx +51 -28
  32. package/src/client/blocks/render-node.tsx +3 -16
  33. package/src/client/blocks/state.ts +18 -14
  34. package/src/client/dom-fence.tsx +2 -1
  35. package/src/client/fence-render.tsx +26 -15
  36. package/src/client/genui-runtime/diagnostics.ts +143 -0
  37. package/src/client/genui-runtime/index.ts +1 -0
  38. package/src/client/genui-runtime/limits.ts +54 -0
  39. package/src/client/genui-runtime/normalize.ts +115 -0
  40. package/src/client/genui-runtime/schema.ts +291 -0
  41. package/src/client/genui-runtime/value-utils.ts +67 -0
  42. package/src/client/guard.ts +314 -169
  43. package/src/client/index.tsx +3 -2
  44. package/src/client/interaction-store.ts +7 -2
  45. package/src/client/panel-command.ts +2 -1
  46. package/src/client/panel.tsx +1 -1
  47. package/src/client/primitive-labels.ts +37 -0
  48. package/src/client/spec.ts +17 -17
  49. package/src/client/toolview.tsx +2 -1
  50. package/src/index.ts +3 -6
  51. package/src/plugin/chart-contract.ts +7 -97
  52. package/src/plugin/index.ts +2 -6
  53. package/src/plugin/tool.ts +60 -40
@@ -101,4 +101,4 @@ export declare const BreadcrumbNode: import("react").NamedExoticComponent<{
101
101
  * names stay independent. The model round-trip takes seconds, so a few
102
102
  * hundred ms of trailing delay is imperceptible — and it stops bursts of
103
103
  * queued user turns.
104
- */
104
+ */
@@ -0,0 +1,17 @@
1
+ import type { GenuiCheckbox } from '../spec.ts';
2
+ import type { AnswersState, GenuiBlockProps } from './state.ts';
3
+ /**
4
+ * Checkbox with two backwards-compatible modes:
5
+ *
6
+ * - no `group`: legacy local toggle + optional per-click action;
7
+ * - with `group`: local-only multi-select state collected by a sibling submit.
8
+ *
9
+ * A persisted group key wins over `checked`, including an explicit empty
10
+ * array. This lets a user clear every default-checked option without those
11
+ * defaults reappearing after refresh.
12
+ */
13
+ export declare function CheckboxNode({ node, onAction, answers }: {
14
+ node: GenuiCheckbox;
15
+ onAction?: GenuiBlockProps['onAction'];
16
+ answers?: AnswersState | undefined;
17
+ }): import("react").JSX.Element;
@@ -7,14 +7,9 @@ export declare function RadioNode({ node, onAction, answers }: {
7
7
  }): import("react").JSX.Element;
8
8
  /** Resolve a question's correct label from its metadata. */
9
9
  export declare function correctLabelOf(m: QuestionMeta): string | undefined;
10
- /** Submit: the "交卷" control of a grouped-radio block. LOCAL-FIRST (v2.6):
11
- * when at least one question carries `answer` data the click grades IN PLACE
12
- * — score, per-question right/wrong, explanations — with zero model round
13
- * trip, and locks the questions until 重新作答 resets them. Only when NO
14
- * question has answers does it fall back to firing ONE action
15
- * (`{type:'submit', answers, total, answered}`). Disabled until the
16
- * selection criteria are met (all listed groups answered, or ≥1 answer
17
- * without a group list); the hint shows the progress. */
10
+ /** Submit: collect grouped radio and checkbox answers. Radio-only scopes keep
11
+ * LOCAL-FIRST grading; when checkbox selections participate, the click falls
12
+ * back to the aggregation action so multi-select data is never discarded. */
18
13
  export declare function SubmitNode({ node, onAction, answers }: {
19
14
  node: GenuiSubmit;
20
15
  onAction?: GenuiBlockProps['onAction'];
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Shared block-level state types: the block props contract and the answers
3
- * registry (grouped radios → submit grading). Lives OUTSIDE GenuiBlock.tsx so
4
- * the per-family block modules can import the types without a cycle back
5
- * into the block shell.
3
+ * registry (grouped radios / checkboxes → submit aggregation). Lives OUTSIDE
4
+ * GenuiBlock.tsx so the per-family block modules can import the types without
5
+ * a cycle back into the block shell.
6
6
  * @module @changfenhuang/dsh-genui/client/blocks/state
7
7
  */
8
8
  import type { GenuiSpec } from '../spec.ts';
@@ -17,10 +17,11 @@ export interface GenuiBlockProps {
17
17
  onAction?: ((action: string, payload: Record<string, unknown>) => void) | undefined;
18
18
  /**
19
19
  * v2.7: durable-state key (session + slot + content fingerprint). When set,
20
- * interaction state (radio answers, submit lock, field values) persists to
21
- * localStorage and restores on refresh / re-render of the same content.
22
- * Changing this key also starts a fresh in-memory interaction-state lifetime;
23
- * callers only provide stateKey — GenuiBlock owns the matching React identity.
20
+ * interaction state (radio answers, checkbox groups, submit lock, field
21
+ * values) persists to localStorage and restores on refresh / re-render of
22
+ * the same content. Changing this key also starts a fresh in-memory
23
+ * interaction-state lifetime; callers only provide stateKey — GenuiBlock
24
+ * owns the matching React identity.
24
25
  */
25
26
  stateKey?: string | undefined;
26
27
  }
@@ -33,15 +34,17 @@ export interface QuestionMeta {
33
34
  /** Shown after local grading. */
34
35
  explanation?: string | undefined;
35
36
  }
36
- /** Block-wide answers registry (v2.5/v2.6): grouped radios record selections
37
- * and register their question metadata here; a sibling `submit` node either
38
- * grades IN PLACE (questions carry `answer` data) or collects everything
39
- * into ONE action. Lives in GenuiBlock state so re-renders keep the
40
- * selections, threaded down through the recursive render walk. Answers are
41
- * plain group → chosen-option-label strings (the display label lives in
42
- * QuestionMeta; localStorage stores the same string table — no migration). */
37
+ /** Block-wide aggregation state. Grouped radios store one selected label in
38
+ * `answers`; grouped checkboxes store a selected-label array in
39
+ * `multiAnswers`. Keeping the registries separate preserves the existing
40
+ * radio/local-grading string contract while allowing submit to expose one
41
+ * unified `answers` payload (`string | string[]`). */
43
42
  export interface AnswersState {
44
43
  answers: Record<string, string>;
44
+ /** Grouped checkbox selections. An empty array is meaningful: the user
45
+ * explicitly cleared the group, so model-provided `checked` defaults must
46
+ * not reappear after a durable restore. */
47
+ multiAnswers: Record<string, string[]>;
45
48
  /** Field values by id (input/textarea with an `id`), collected by submit. */
46
49
  fields: Record<string, string>;
47
50
  /** Field ids whose value must never be persisted or collected (secrets). */
@@ -52,6 +55,7 @@ export interface AnswersState {
52
55
  /** Bumped by every reset; radios use it as their remount key. */
53
56
  round: number;
54
57
  setAnswer: (group: string, choice: string) => void;
58
+ setMultiAnswer: (group: string, choice: string, checked: boolean) => void;
55
59
  setField: (id: string, value: string) => void;
56
60
  registerSecretField: (id: string) => void;
57
61
  registerMeta: (group: string, meta: QuestionMeta) => void;
@@ -1,6 +1,6 @@
1
1
  import { type Root } from 'react-dom/client';
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
- import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
4
4
  /** Override the React root factory (tests / tuning). */
5
5
  export declare function setDomRootFactory(factory: (container: HTMLElement) => Root): void;
6
6
  /**
@@ -0,0 +1,18 @@
1
+ export interface GenuiDiagnostic {
2
+ readonly kind: 'alias' | 'unknown-field';
3
+ readonly path: string;
4
+ readonly message: string;
5
+ readonly type?: string;
6
+ readonly field?: string;
7
+ readonly canonical?: string;
8
+ }
9
+ /**
10
+ * Diagnose unknown direct fields on native nodes.
11
+ *
12
+ * Unknown types are intentionally skipped so custom renderers retain their
13
+ * opaque extension payloads. Native unknown fields are warnings, not errors.
14
+ *
15
+ * @param value - Canonical or raw GenUI value.
16
+ * @returns Stable field diagnostics in tree order.
17
+ */
18
+ export declare function diagnoseUnknownGenuiFields(value: unknown): GenuiDiagnostic[];
@@ -0,0 +1 @@
1
+ export { GENUI_LIMITS } from './limits.ts';
@@ -0,0 +1,54 @@
1
+ /** Resource limits shared by GenUI repair, validation, and rendering. */
2
+ export declare const GENUI_LIMITS: {
3
+ /** Maximum nesting depth of the component tree. */
4
+ readonly maxDepth: 8;
5
+ /** Maximum total nodes across the whole spec. */
6
+ readonly maxNodes: 200;
7
+ /** Maximum length of any plain string field. */
8
+ readonly maxString: 2000;
9
+ /** Maximum length of a `code` body. */
10
+ readonly maxCode: 12000;
11
+ /** Maximum length of a mermaid source. */
12
+ readonly maxMermaid: 8000;
13
+ /** Maximum `grid` columns. */
14
+ readonly maxGridCols: 12;
15
+ /** Maximum `tabs` count. */
16
+ readonly maxTabs: 12;
17
+ /** Maximum `accordion` items. */
18
+ readonly maxAccordionItems: 24;
19
+ /** Maximum `list` items. */
20
+ readonly maxListItems: 50;
21
+ /** Maximum `select`/`radio` options. */
22
+ readonly maxOptions: 50;
23
+ /** Maximum `table` rows / columns. */
24
+ readonly maxTableRows: 50;
25
+ readonly maxTableCols: 12;
26
+ /** Maximum `chart` data points per series. */
27
+ readonly maxChartPoints: 60;
28
+ /** Maximum `plot` series and per-series parameters. */
29
+ readonly maxPlotSeries: 8;
30
+ readonly maxPlotParams: 6;
31
+ /** Maximum `scene3d` meshes. */
32
+ readonly maxMeshes: 5;
33
+ /** Maximum `quiz` options. */
34
+ readonly maxQuizOptions: 8;
35
+ /** Maximum `steps` / `timeline` / `breadcrumb` / `keyvalue` entries. */
36
+ readonly maxSteps: 24;
37
+ readonly maxTimelineItems: 24;
38
+ readonly maxBreadcrumbItems: 12;
39
+ readonly maxKeyValuePairs: 24;
40
+ /** Maximum `file-tree` nesting. */
41
+ readonly maxTreeDepth: 6;
42
+ /** Maximum `diagram` nodes / edges / zones / focal accents. */
43
+ readonly maxDiagramNodes: 9;
44
+ readonly maxDiagramEdges: 12;
45
+ readonly maxDiagramZones: 3;
46
+ readonly maxDiagramFocal: 2;
47
+ readonly maxDiagramLabel: 14;
48
+ /** Maximum depth of an `echart` option object. */
49
+ readonly maxEChartOptionDepth: 10;
50
+ /** Maximum length of any single array inside an `echart` option. */
51
+ readonly maxEChartArrayLen: 500;
52
+ /** Maximum entries traversed while sanitizing an `echart` option. */
53
+ readonly maxEChartOptionNodes: 2000;
54
+ };
@@ -0,0 +1,15 @@
1
+ import type { GenuiDiagnostic } from './diagnostics.ts';
2
+ /**
3
+ * Normalize a raw GenUI value into canonical field names.
4
+ *
5
+ * Only deterministic aliases and structural aliases are changed. Resource
6
+ * limits, type repair, security filtering, and semantic validation remain in
7
+ * the guard layer. Unknown component types are returned opaque.
8
+ *
9
+ * @param value - Raw GenUI spec or bare native component.
10
+ * @returns Canonical value and stable alias diagnostics.
11
+ */
12
+ export declare function normalizeGenuiSpec(value: unknown): {
13
+ value: unknown;
14
+ warnings: GenuiDiagnostic[];
15
+ };
@@ -0,0 +1,73 @@
1
+ /** Runtime metadata shared by GenUI normalization and diagnostics. */
2
+ export type ComponentFieldKind = 'string' | 'string-or-null' | 'number' | 'boolean' | 'nodes' | 'array' | 'object' | 'unknown';
3
+ /** A conditional rule for fields whose presence depends on another field. */
4
+ export interface ComponentConditionalRule {
5
+ readonly kind: 'required-if';
6
+ readonly when: {
7
+ readonly field: string;
8
+ readonly equals: unknown;
9
+ };
10
+ readonly required: readonly string[];
11
+ readonly message?: string;
12
+ }
13
+ /** A field rule that requires at least one member of a field group. */
14
+ export interface ComponentOneOfRule {
15
+ readonly kind: 'one-of-required';
16
+ readonly fields: readonly string[];
17
+ readonly message?: string;
18
+ }
19
+ /** Runtime metadata naming a component-specific semantic validator. */
20
+ export interface ComponentValidatorMetadata {
21
+ readonly name: string;
22
+ readonly [key: string]: unknown;
23
+ }
24
+ /** Runtime schema for an object nested inside a native component field. */
25
+ export interface ComponentRecordSchema {
26
+ readonly required: readonly string[];
27
+ readonly fields: Readonly<Record<string, ComponentFieldKind>>;
28
+ readonly enums: Readonly<Record<string, readonly string[]>>;
29
+ readonly nested: Readonly<Record<string, ComponentRecordSchema>>;
30
+ }
31
+ export interface ComponentSchema {
32
+ readonly required: readonly string[];
33
+ readonly fields: Readonly<Record<string, ComponentFieldKind>>;
34
+ readonly enums: Readonly<Record<string, readonly string[]>>;
35
+ /** Explicit optional field kinds; `fields` remains the complete field map. */
36
+ readonly optional: Readonly<Record<string, ComponentFieldKind>>;
37
+ readonly aliases: Readonly<Record<string, string>>;
38
+ readonly oneOfRequired: readonly (readonly string[])[];
39
+ readonly conditionalRequired: readonly ComponentConditionalRule[];
40
+ readonly rules: readonly (ComponentOneOfRule | ComponentConditionalRule)[];
41
+ readonly nested: Readonly<Record<string, ComponentRecordSchema>>;
42
+ readonly validator?: ComponentValidatorMetadata;
43
+ }
44
+ /** Canonical enum domains shared by schema validation and repair. */
45
+ export declare const TEXT_SIZES: readonly ["h1", "h2", "h3", "body", "muted", "caption"];
46
+ export declare const BUTTON_TONES: readonly ["primary", "danger", "success", "ghost"];
47
+ export declare const BADGE_TONES: readonly ["success", "warn", "danger", "accent"];
48
+ export declare const INPUT_TYPES: readonly ["text", "email", "password", "color"];
49
+ export declare const CALLOUT_TONES: readonly ["info", "success", "warning", "error"];
50
+ export declare const CHART_KINDS: readonly ["bars", "line", "donut"];
51
+ export declare const PLOT_KINDS: readonly ["line", "area", "scatter"];
52
+ export declare const MEDIA_ASPECT_RATIOS: readonly ["16:9", "4:3", "1:1", "9:16"];
53
+ export declare const MESH_SHAPES: readonly ["box", "sphere", "cone", "cylinder", "torus"];
54
+ export declare const FILE_TYPES: readonly ["file", "dir"];
55
+ export declare const DIAGRAM_KINDS: readonly ["architecture", "it-state", "flowchart", "sequence", "state", "er", "timeline", "swimlane", "quadrant", "radar", "loop", "nested", "tree", "org-chart", "layers", "venn", "pyramid", "bar", "line", "gantt", "scatter", "high-level", "process", "medallion", "data-flow", "dp-integration", "dp-security-matrix"];
56
+ export declare const DIAGRAM_NODE_TYPES: readonly ["focal", "backend", "store", "external", "input", "optional", "security"];
57
+ export declare const DIAGRAM_VARIANTS: readonly ["light", "dark", "editorial"];
58
+ export declare const DIAGRAM_EDGE_KINDS: readonly ["solid", "dashed", "accent", "link"];
59
+ export declare const DIAGRAM_ROUTES: readonly ["auto", "orthogonal", "straight"];
60
+ export declare const ECHART_PRESETS: readonly ["bar", "line", "area", "pie", "scatter"];
61
+ /** Root GenUI specification metadata used by diagnostics. */
62
+ export declare const GENUI_SPEC_SCHEMA: ComponentSchema;
63
+ /** Backwards-friendly short alias for the root specification schema. */
64
+ export declare const SPEC_SCHEMA: ComponentSchema;
65
+ /**
66
+ * Native component field metadata.
67
+ *
68
+ * This is intentionally explicit rather than inferred from TypeScript
69
+ * interfaces: the registry is also consumed at runtime by normalization and
70
+ * diagnostics, where erased interfaces are unavailable.
71
+ */
72
+ export declare const COMPONENT_SCHEMAS: Readonly<Record<string, ComponentSchema>>;
73
+ export declare const GENUI_NATIVE_TYPES: ReadonlySet<string>;
@@ -0,0 +1,18 @@
1
+ /** Shared primitive sanitizers used by the GenUI guard. */
2
+ /** String field: truncate a string to `cap`, or undefined when not a string. */
3
+ export declare function str(value: unknown, cap: number): string | undefined;
4
+ export declare function color(value: unknown): string | undefined;
5
+ /** Keep only http(s) and mailto link targets. */
6
+ export declare function safeHref(value: unknown): string | undefined;
7
+ /** Keep browser-reachable http(s) or same-origin relative media paths. */
8
+ export declare function safeMediaSrc(value: unknown): string | undefined;
9
+ /** Finite-number field: clamp into [min, max], or undefined when not finite. */
10
+ export declare function num(value: unknown, min: number, max: number): number | undefined;
11
+ /** Integer field: clamp into [min, max], or undefined when not a finite number. */
12
+ export declare function int(value: unknown, min: number, max: number): number | undefined;
13
+ /** Optional enum field: the value when it matches, otherwise undefined. */
14
+ export declare function enu<T extends string>(value: unknown, values: readonly T[]): T | undefined;
15
+ /** Plain object (not array, not null). */
16
+ export declare function obj(value: unknown): Record<string, unknown> | undefined;
17
+ /** Preserve an optional field only when its value exists. */
18
+ export declare function opt<K extends string, V>(key: K, value: V | undefined): Partial<Record<K, V>>;
@@ -19,66 +19,7 @@
19
19
  * are elided.
20
20
  */
21
21
  import type { GenuiSpec } from './spec.ts';
22
- /** Hard resource limits enforced by repair (and mirrored at render time). */
23
- export declare const GENUI_LIMITS: {
24
- /** Maximum nesting depth of the component tree. */
25
- readonly maxDepth: 8;
26
- /** Maximum total nodes across the whole spec. */
27
- readonly maxNodes: 200;
28
- /** Maximum length of any plain string field. */
29
- readonly maxString: 2000;
30
- /** Maximum length of a `code` body. */
31
- readonly maxCode: 12000;
32
- /** Maximum length of a mermaid source. */
33
- readonly maxMermaid: 8000;
34
- /** Maximum `grid` columns. */
35
- readonly maxGridCols: 12;
36
- /** Maximum `tabs` count. */
37
- readonly maxTabs: 12;
38
- /** Maximum `accordion` items. */
39
- readonly maxAccordionItems: 24;
40
- /** Maximum `list` items. */
41
- readonly maxListItems: 50;
42
- /** Maximum `select`/`radio` options. */
43
- readonly maxOptions: 50;
44
- /** Maximum `table` rows / columns. */
45
- readonly maxTableRows: 50;
46
- readonly maxTableCols: 12;
47
- /** Maximum `chart` data points per series. */
48
- readonly maxChartPoints: 60;
49
- /** Maximum `plot` series and per-series parameters. */
50
- readonly maxPlotSeries: 8;
51
- readonly maxPlotParams: 6;
52
- /** Maximum `scene3d` meshes. */
53
- readonly maxMeshes: 5;
54
- /** Maximum `quiz` options. */
55
- readonly maxQuizOptions: 8;
56
- /** Maximum `steps` / `timeline` / `breadcrumb` / `keyvalue` entries. */
57
- readonly maxSteps: 24;
58
- readonly maxTimelineItems: 24;
59
- readonly maxBreadcrumbItems: 12;
60
- readonly maxKeyValuePairs: 24;
61
- /** Maximum `file-tree` nesting. */
62
- readonly maxTreeDepth: 6;
63
- /** Maximum `diagram` nodes / edges / zones / focal accents (editorial
64
- * complexity budget, mirroring diagram-design's §7 limits). */
65
- readonly maxDiagramNodes: 9;
66
- readonly maxDiagramEdges: 12;
67
- readonly maxDiagramZones: 3;
68
- readonly maxDiagramFocal: 2;
69
- readonly maxDiagramLabel: 14;
70
- /** Maximum depth of an `echart` option object (prevents pathological nested
71
- * ECharts configs from stalling the guard walk). */
72
- readonly maxEChartOptionDepth: 10;
73
- /** Maximum length of any single array inside an `echart` option (prevents
74
- * a model from stalling rendering with `series.data` of hundreds of
75
- * thousands of points). */
76
- readonly maxEChartArrayLen: 500;
77
- /** Maximum total entries (object keys + array elements) traversed while
78
- * sanitizing an `echart` option. Bounds the walk so a pathologically
79
- * large option object cannot stall the guard. */
80
- readonly maxEChartOptionNodes: 2000;
81
- };
22
+ import type { GenuiDiagnostic } from './genui-runtime/diagnostics.ts';
82
23
  /** Result of `validateGenuiSpec`. */
83
24
  export interface GenuiValidation {
84
25
  ok: boolean;
@@ -86,12 +27,14 @@ export interface GenuiValidation {
86
27
  errors: string[];
87
28
  }
88
29
  /**
89
- * Deterministically repair a raw spec value into a renderable GenuiSpec.
90
- * Returns null only when the root is not an object with an `items` array
91
- * (a bare component root is wrapped into a col first — the documented fence
92
- * vocabulary allows single-component bodies); every other defect is healed by
93
- * dropping/clamping/truncating. Idempotent: repairing a repaired spec is a
94
- * no-op.
30
+ * Deterministically repair a raw spec into a renderable GenuiSpec.
31
+ *
32
+ * Alias normalization is performed before the existing resource, type, and
33
+ * security repair rules. The result remains idempotent and keeps the legacy
34
+ * public API used by the fence renderer and client components.
35
+ *
36
+ * @param value - Raw GenUI spec or bare component.
37
+ * @returns Repaired canonical spec, or null for an invalid root.
95
38
  */
96
39
  export declare function repairGenuiSpec(value: unknown): GenuiSpec | null;
97
40
  /**
@@ -115,6 +58,8 @@ export declare const GENUI_NODE_TYPES: ReadonlySet<string>;
115
58
  * @returns declared node count up to the ceiling.
116
59
  */
117
60
  export declare function countDeclaredGenuiNodes(value: unknown, cap?: number): number;
61
+ /** Count native nodes that survived repair, excluding opaque custom nodes. */
62
+ export declare function countRenderedNativeGenuiNodes(value: unknown, cap?: number): number;
118
63
  /**
119
64
  * Return field-level chart errors without changing other repairable component families.
120
65
  * @param value - raw GenUI spec.
@@ -122,10 +67,54 @@ export declare function countDeclaredGenuiNodes(value: unknown, cap?: number): n
122
67
  */
123
68
  export declare function validateGenuiChartSemantics(value: unknown): string[];
124
69
  /**
125
- * Validate a raw spec value against the white list and limits, collecting
126
- * human-readable problems. Unlike repair this never mutates: it is a
127
- * diagnostic for tests and tooling. Unknown `type`s are reported (a plugin
128
- * custom type is valid only when a renderer is registered — the guard cannot
129
- * know, so it flags them as warnings).
70
+ * Validate a raw GenUI value after deterministic alias normalization.
71
+ *
72
+ * Validation therefore only sees canonical fields; repairable aliases do not
73
+ * produce false required-field errors. Structural and semantic defects remain
74
+ * errors, while native unknown fields are exposed by the warning diagnostics
75
+ * returned from `processGenuiSpec`.
76
+ *
77
+ * @param value - Raw GenUI spec or bare component.
78
+ * @returns Validation result with human-readable errors.
130
79
  */
131
80
  export declare function validateGenuiSpec(value: unknown): GenuiValidation;
81
+ export interface GenuiProcessResult {
82
+ /** Canonical alias-normalized input value. */
83
+ value: unknown;
84
+ /** Alias-normalized value, provided as an explicit descriptive alias. */
85
+ normalized: unknown;
86
+ /** Canonical repaired value consumed by the renderer. */
87
+ repaired: GenuiSpec | null;
88
+ /** Renderer-facing alias for `repaired`. */
89
+ spec: GenuiSpec | null;
90
+ /** Structural and semantic validation errors. */
91
+ errors: string[];
92
+ /** Alias and native unknown-field warnings. */
93
+ warnings: GenuiDiagnostic[];
94
+ /** Native nodes declared in the canonical input, before repair drops. */
95
+ declaredCount: number;
96
+ /** Nodes present in the repaired tree. */
97
+ renderedCount: number;
98
+ /** Native nodes declared before repair, excluding custom payloads. */
99
+ declaredNativeCount: number;
100
+ /** Native nodes present after repair, excluding custom and file-tree data. */
101
+ renderedNativeCount: number;
102
+ /** All rendered component nodes, including opaque custom nodes. */
103
+ renderedTotalCount: number;
104
+ }
105
+ /**
106
+ * Run the shared canonical GenUI pipeline.
107
+ *
108
+ * The pipeline normalizes aliases, applies the existing deterministic repair
109
+ * and security filters, validates the canonical input, and reports native
110
+ * declarations that disappeared during repair. Custom nodes stay opaque and
111
+ * do not participate in native unknown-field diagnostics.
112
+ *
113
+ * @param value - Raw GenUI spec or bare component.
114
+ * @returns Canonical input, repaired output, diagnostics, and node counts.
115
+ */
116
+ export declare function processGenuiSpec(value: unknown): GenuiProcessResult;
117
+ /** Return whether the only process errors describe an intentional budget tail cut. */
118
+ export declare function isIntentionalBudgetCut(processed: GenuiProcessResult): boolean;
119
+ /** Decide whether a repaired spec is safe to expose to any GenUI renderer. */
120
+ export declare function isRenderableProcess(processed: GenuiProcessResult): boolean;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Interaction-state store: durable per-block GenUI interaction state
3
- * (radio answers, submit lock, input/textarea values) in localStorage.
3
+ * (radio answers, checkbox groups, submit lock, input/textarea values) in
4
+ * localStorage.
4
5
  *
5
6
  * LOCAL-FIRST persistence: a ```dsh-ui block's interactive state survives
6
7
  * page refresh and session reopen because it is keyed by
@@ -10,13 +11,17 @@
10
11
  * share state because their content fingerprints differ.
11
12
  *
12
13
  * Bounded: at most MAX_BLOCKS entries, LRU-evicted on write; each block's
13
- * payload is small (answers map + a few field values).
14
+ * payload is small (answer maps + a few field values).
14
15
  * @module @changfenhuang/dsh-genui/client/interaction-store
15
16
  */
16
17
  /** Durable state of one UI block. */
17
18
  export interface BlockInteractionState {
18
19
  /** group → chosen option label (radio aggregation answers). */
19
20
  answers?: Record<string, string>;
21
+ /** group → chosen option labels (checkbox aggregation answers). Empty arrays
22
+ * are retained so an explicitly cleared group wins over `checked` defaults
23
+ * after refresh. */
24
+ multiAnswers?: Record<string, string[]>;
20
25
  /** True after a local grading: the paper stays graded across refresh. */
21
26
  locked?: boolean;
22
27
  /** field id → current value (input/textarea with an `id`). */
@@ -15,7 +15,7 @@
15
15
  * re-run render_ui) or through another /panel.
16
16
  */
17
17
  import type { InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
18
- import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client';
18
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
19
19
  import type { GenuiSpec } from './spec.ts';
20
20
  /** Default panel content published by `/panel`: the component overview. */
21
21
  export declare const DEFAULT_PANEL_SPEC: GenuiSpec;
@@ -0,0 +1,11 @@
1
+ /** Localized labels required by the rc.1 DSH UI primitives. */
2
+ import type { DiffBlockLabels, JsonTreeLabels } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ /** Chrome labels for an inline GenUI diff block. */
4
+ export declare const DIFF_BLOCK_LABELS: DiffBlockLabels;
5
+ /** Copy labels for a code block rendered inside a GenUI fence or node. */
6
+ export declare const CODE_BLOCK_LABELS: {
7
+ readonly copyLabel: "复制";
8
+ readonly copiedLabel: "复制成功";
9
+ };
10
+ /** Chrome labels for an inline GenUI JSON tree. */
11
+ export declare const JSON_TREE_LABELS: JsonTreeLabels;
@@ -51,7 +51,7 @@ export interface GenuiInput {
51
51
  label?: string;
52
52
  placeholder?: string;
53
53
  value?: string;
54
- inputType?: 'text' | 'email' | 'password';
54
+ inputType?: 'text' | 'email' | 'password' | 'color';
55
55
  /** v2: when set, interaction sends this action back to the model. */
56
56
  action?: string;
57
57
  /**
@@ -84,6 +84,12 @@ export interface GenuiCheckbox {
84
84
  checked?: boolean;
85
85
  /** v2: when set, interaction sends this action back to the model. */
86
86
  action?: string;
87
+ /**
88
+ * Aggregation group name. When set, toggles stay local and update the
89
+ * block-wide multi-answer registry instead of firing the per-click action;
90
+ * a sibling `submit` collects the selected labels as a string array.
91
+ */
92
+ group?: string;
87
93
  }
88
94
  export interface GenuiLink {
89
95
  type: 'link';
@@ -314,23 +320,17 @@ export interface GenuiRadio {
314
320
  /** v2.6: per-question explanation shown after local grading. */
315
321
  explanation?: string;
316
322
  }
317
- /** Submit node: collects the answers of sibling `radio` groups in this block.
318
- * LOCAL-FIRST: when the questions carry `answer` data the click grades IN
319
- * PLACE — score, per-question right/wrong, explanations — with no model
320
- * round trip, and locks the questions until "重新作答" resets them. Only when
321
- * NO question carries answers does it fall back to firing ONE action
322
- * (`{type:'submit', answers, total, answered}`). */
323
+ /** Submit node: collects sibling `radio` and grouped `checkbox` answers in
324
+ * this block. LOCAL-FIRST grading still applies to radio-only question scopes;
325
+ * aggregation scopes can emit strings for radio groups and string arrays for
326
+ * checkbox groups in the same `answers` object. */
323
327
  export interface GenuiSubmit {
324
328
  type: 'submit';
325
329
  label: string;
326
330
  /**
327
- * Optional action name. Local-first: when ANY question in scope carries
328
- * `answer` data the click grades IN PLACE with zero model round trip, so
329
- * no action is needed — the spec stays valid without one. Only when NO
330
- * question has local answers does the submit need an action to collect
331
- * `{type:'submit', answers, total, answered}`; without one the button
332
- * renders disabled (honest affordance). Also fired as the reset hook if
333
- * `resetAction` is absent.
331
+ * Optional action name. Local-first: when the in-scope questions can be
332
+ * graded entirely locally, no action is needed. Aggregation submits require
333
+ * an action and emit `{type:'submit', answers, total, answered}`.
334
334
  */
335
335
  action?: string;
336
336
  /**
@@ -341,9 +341,9 @@ export interface GenuiSubmit {
341
341
  resetAction?: string;
342
342
  /**
343
343
  * Optional explicit group list to wait for; when absent the submit enables
344
- * once at least one grouped radio has an answer. When present it stays
345
- * disabled until EVERY listed group has a recorded answer (the hint shows
346
- * the progress).
344
+ * once at least one grouped answer (or filled field) exists. When present
345
+ * it stays disabled until EVERY listed radio/checkbox group has a non-empty
346
+ * recorded answer (the hint shows the progress).
347
347
  */
348
348
  groups?: string[];
349
349
  }
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Validate the native chart contract all the way to something the renderer
3
- * can actually draw. The shared guard still owns field/type validation; this
4
- * layer adds renderer-specific invariants that would otherwise produce an
5
- * empty line/donut chart or a grouped-bars shell with no points.
2
+ * Validate chart nodes using the guard's shared native renderability contract.
3
+ * The plugin-facing API remains stable while the guard remains the single
4
+ * owner of chart field and renderer-semantic rules.
5
+ *
6
+ * @param value - Raw GenUI spec or bare chart node.
7
+ * @returns Chart errors in deterministic tree order.
6
8
  */
7
9
  export declare function validateRenderableChartSemantics(value: unknown): string[];
@@ -5,13 +5,10 @@
5
5
  * model the language exists, so a session without the plugin simply never
6
6
  * emits fences and nothing changes.
7
7
  *
8
- * The section is a convention section (order 100-199), placed after the bash
9
- * guidance so the model sees it among its output-format rules.
8
+ * The section uses the host's centrally allocated structured-output placement.
10
9
  * @module @changfenhuang/dsh-genui
11
10
  */
12
11
  import { Context } from '@deepseek-ai/cordis';
13
- /** Convention: tool guidance uses 100–199; bash's section is 104. */
14
- export declare const GENUI_SECTION_ORDER = 105;
15
12
  /** The fence language description injected into every assembled system prompt.
16
13
  * Deliberately slim: the `genui` skill carries the full component→field
17
14
  * mapping; this section keeps only the contract that must always be