@aquera/mcp-ui-render 0.0.1

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 (42) hide show
  1. package/CHANGELOG.md +437 -0
  2. package/CONFIGURATION.md +178 -0
  3. package/LICENSE +21 -0
  4. package/README.md +237 -0
  5. package/RELEASE-NOTES.md +77 -0
  6. package/TEST-PLAN.md +72 -0
  7. package/USAGE.md +100 -0
  8. package/package.json +52 -0
  9. package/src/controls.d.ts +1 -0
  10. package/src/controls.js +572 -0
  11. package/src/controls.js.map +1 -0
  12. package/src/elements/aq-mcp-config.d.ts +289 -0
  13. package/src/elements/aq-mcp-config.js +673 -0
  14. package/src/elements/aq-mcp-config.js.map +1 -0
  15. package/src/elements/aq-mcp-field.d.ts +92 -0
  16. package/src/elements/aq-mcp-field.js +275 -0
  17. package/src/elements/aq-mcp-field.js.map +1 -0
  18. package/src/elements/aq-mcp-section.d.ts +101 -0
  19. package/src/elements/aq-mcp-section.js +261 -0
  20. package/src/elements/aq-mcp-section.js.map +1 -0
  21. package/src/engine/status.d.ts +31 -0
  22. package/src/engine/status.js +44 -0
  23. package/src/engine/status.js.map +1 -0
  24. package/src/engine/submit.d.ts +26 -0
  25. package/src/engine/submit.js +37 -0
  26. package/src/engine/submit.js.map +1 -0
  27. package/src/engine/validate.d.ts +24 -0
  28. package/src/engine/validate.js +144 -0
  29. package/src/engine/validate.js.map +1 -0
  30. package/src/engine/values.d.ts +20 -0
  31. package/src/engine/values.js +42 -0
  32. package/src/engine/values.js.map +1 -0
  33. package/src/index.d.ts +18 -0
  34. package/src/index.js +20 -0
  35. package/src/index.js.map +1 -0
  36. package/src/mcp-ui-render.css +811 -0
  37. package/src/registry.d.ts +63 -0
  38. package/src/registry.js +39 -0
  39. package/src/registry.js.map +1 -0
  40. package/src/types.d.ts +239 -0
  41. package/src/types.js +8 -0
  42. package/src/types.js.map +1 -0
@@ -0,0 +1,261 @@
1
+ var AqMcpSection_1;
2
+ import { __decorate, __metadata } from "tslib";
3
+ /**
4
+ * `<aq-mcp-section>` — one section rendered as a card (design Option A): header
5
+ * with the section label, an `authType` badge, and a save-target caption
6
+ * ("connector saves · <submitTool>" or "platform saves · applications_update",
7
+ * §13); a body of two-column fields (applicable, non-hidden, in order, §12,
8
+ * and — for `test-connection-report`/`permissions-report` specifically — only
9
+ * once there's actually a value to show, see `hasNothingToShow`); and a save
10
+ * footer whose button emits `section-submit`. A host may register
11
+ * a custom footer for `section.key` via `registerSectionFooter` to replace the
12
+ * default button entirely; absent that, the default "Save <label>" renders.
13
+ * `showFooter` (default true) is a master switch that hides EITHER — custom
14
+ * or default — when false, e.g. when the host drives submission itself.
15
+ * `compact` (set by `<aq-mcp-config>` in `sectionsLayout: 'tabs'` mode) drops
16
+ * the card's own border + title, since the tab nav already shows the label.
17
+ *
18
+ * Light DOM by design (see `<aq-mcp-config>`'s own doc comment) — requires
19
+ * the host to load `mcp-ui-render.css` globally.
20
+ */
21
+ import { LitElement, html } from 'lit';
22
+ import { customElement, property } from 'lit/decorators.js';
23
+ import { fieldApplies } from '../engine/values.js';
24
+ import { sectionHasEditableFields } from '../engine/submit.js';
25
+ import { resolveSectionFooter } from '../registry.js';
26
+ import './aq-mcp-field.js';
27
+ /**
28
+ * `ui-component`s whose control renders NOTHING when there's no value yet
29
+ * (`test-connection-report`/`permissions-report`'s own "null renders nothing"
30
+ * rule, see `controls.ts`) — distinct from `properties-table`, which always
31
+ * shows its own header/toolbar even with zero rows, so it's deliberately not
32
+ * in this set. Without this filter, `<aq-mcp-field>` still renders its usual
33
+ * label-left/control-right row for one of these with the right column blank —
34
+ * a label dangling next to nothing, e.g. "Validation Report" shown before a
35
+ * host has ever run its test. A field in this set with no value yet is
36
+ * skipped entirely (no `<aq-mcp-field>` element at all, not even an empty
37
+ * one), so nothing — not even the row's own padding/border — is left behind.
38
+ */
39
+ const HIDE_ENTIRELY_WHEN_EMPTY = new Set(['test-connection-report', 'permissions-report']);
40
+ function hasNothingToShow(field, values) {
41
+ if (!HIDE_ENTIRELY_WHEN_EMPTY.has(field['ui-component']))
42
+ return false;
43
+ const value = values[field.attribute];
44
+ return value === null || value === undefined;
45
+ }
46
+ let AqMcpSection = AqMcpSection_1 = class AqMcpSection extends LitElement {
47
+ constructor() {
48
+ super(...arguments);
49
+ this.values = {};
50
+ this.errors = {};
51
+ /** Host-fed options for `select`/`radio` fields resolved dynamically via `field.tool`
52
+ * (§9.6) — see `<aq-mcp-config>`'s own `dynamicOptions` property for the full contract. */
53
+ this.dynamicOptions = {};
54
+ /** Master switch for this section's footer (§13/§14) — the default "Save
55
+ * <label>" button, AND any `registerSectionFooter` override. Default true;
56
+ * set false to hide the footer entirely regardless of what's registered,
57
+ * e.g. when the host drives submission itself via its own button(s). */
58
+ this.showFooter = true;
59
+ /** Set by `<aq-mcp-config>` when this section is rendered as a tab panel
60
+ * (`sectionsLayout: 'tabs'`) — drops the card's own border and title
61
+ * (the tab nav already shows the label) to avoid double-boxing/duplicate
62
+ * labeling. The authType badge and save-target caption still render. */
63
+ this.compact = false;
64
+ /** Set by `<aq-mcp-config>` when this section is the body of a
65
+ * `nile-accordion` (`sectionsLayout: 'accordion'`). Drops the card AND the
66
+ * whole header — the accordion's own summary already carries the label,
67
+ * authType badge and completion chips — and lays the body out as a
68
+ * two-column grid of label-above-control fields (`dense` is passed down to
69
+ * every `<aq-mcp-field>`). The save-target caption moves into the footer,
70
+ * the only place it still has anything to explain, and only when a footer
71
+ * actually renders. */
72
+ this.dense = false;
73
+ /** Column ceiling for the dense field grid — see `<aq-mcp-config>`'s
74
+ * `accordionColumns`. Only meaningful when `dense` is set. */
75
+ this.columns = 2;
76
+ }
77
+ createRenderRoot() {
78
+ return this;
79
+ }
80
+ caption() {
81
+ return this.section.submitTool
82
+ ? `connector saves · ${this.section.submitTool}`
83
+ : 'platform saves · applications_update';
84
+ }
85
+ saveLabel() {
86
+ return `Save ${(this.section.label ?? 'changes').toLowerCase()}`;
87
+ }
88
+ emitSubmit() {
89
+ this.dispatchEvent(new CustomEvent('section-submit', { detail: { sectionKey: this.section.key }, bubbles: true, composed: true }));
90
+ }
91
+ renderFooter(showSave) {
92
+ if (!showSave || !this.showFooter)
93
+ return html ``;
94
+ const custom = resolveSectionFooter(this.section.key);
95
+ if (custom) {
96
+ return custom({ section: this.section, values: this.values, onSubmit: () => this.emitSubmit() });
97
+ }
98
+ return html `<div class="aq-mcp-section__footer">
99
+ ${this.dense ? html `<div class="aq-mcp-section__caption">${this.caption()}</div>` : ''}
100
+ <nile-button variant="primary" @click=${() => this.emitSubmit()}>${this.saveLabel()}</nile-button>
101
+ </div>`;
102
+ }
103
+ renderHeader() {
104
+ const s = this.section;
105
+ // Accordion mode: the accordion's own summary is the header. Rendering a
106
+ // second one here would repeat the label and badge inside the panel.
107
+ if (this.dense)
108
+ return html ``;
109
+ const badge = s.authType ? html `<nile-badge variant="info">${s.authType}</nile-badge>` : '';
110
+ if (this.compact) {
111
+ return html `<div class="aq-mcp-section__header aq-mcp-section__header--compact">
112
+ ${badge}
113
+ <div class="aq-mcp-section__caption">${this.caption()}</div>
114
+ </div>`;
115
+ }
116
+ return html `<div class="aq-mcp-section__header">
117
+ <div class="aq-mcp-section__title">${s.label} ${badge}</div>
118
+ <div class="aq-mcp-section__caption">${this.caption()}</div>
119
+ </div>`;
120
+ }
121
+ /**
122
+ * Split the dense (accordion) body into the three zones the design calls for.
123
+ *
124
+ * The `bootstrap` descriptor has NO layout vocabulary — no group, column,
125
+ * width or span key — and its shape is fixed, so this cannot be declared and
126
+ * has to be derived. It is derived only from data the descriptor already
127
+ * carries, never from the attribute name or anything else guessed:
128
+ *
129
+ * - **toggles** — `datatype: "boolean"`. Last, full width. A switch with a
130
+ * one-line description is a different row shape from a labelled control,
131
+ * which is why these group even though it reorders them.
132
+ * - **defaults** — has a `defaultValue`, is NOT required, and is not a
133
+ * credential. Two-up, below a plain rule. The rule carries NO caption: a
134
+ * heading there ("Defaults you can tune") would be the renderer
135
+ * describing the connector's own fields in words the descriptor never
136
+ * supplied. The separation is conveyed by position and density instead,
137
+ * which needs no invented copy.
138
+ * - **primary** — everything else, full width, first: required fields,
139
+ * credentials, and optional fields the connector gave no default for.
140
+ * These are the ones where the user has to supply something.
141
+ *
142
+ * The `isRequired`/`secret` exclusions matter: a required field that happens
143
+ * to carry a default is still something the user must confirm, and a stored
144
+ * credential is never "a default you can tune" — filing either under that
145
+ * heading would tell the reader they can skip it.
146
+ *
147
+ * This DOES reorder relative to the descriptor's `fields` order, and it is
148
+ * the only layout that does. Order is preserved WITHIN each zone, so a
149
+ * connector's intended sequence still shows through.
150
+ */
151
+ zones(applied) {
152
+ const primary = [];
153
+ const defaults = [];
154
+ const toggles = [];
155
+ for (const f of applied) {
156
+ if (f.datatype === 'boolean')
157
+ toggles.push(f);
158
+ else if (AqMcpSection_1.isTunableDefault(f))
159
+ defaults.push(f);
160
+ else
161
+ primary.push(f);
162
+ }
163
+ return { primary, defaults, toggles };
164
+ }
165
+ /** See `zones()` — "the connector already chose an answer, and you may
166
+ * leave it alone". `datatype: "none"` (buttons, reports) has no value to
167
+ * default; a required field must still be confirmed; a secret is never a
168
+ * tunable default however it was seeded. */
169
+ static isTunableDefault(f) {
170
+ return (f.datatype !== 'none' &&
171
+ f.defaultValue !== undefined &&
172
+ !f.isRequired &&
173
+ !f.secret &&
174
+ !f.writeOnce);
175
+ }
176
+ renderFields(fields) {
177
+ return fields.map((f) => html `<aq-mcp-field
178
+ class="aq-mcp-field ${this.dense ? 'aq-mcp-field--dense' : ''}"
179
+ data-attr=${f.attribute}
180
+ .field=${f}
181
+ .value=${this.values[f.attribute]}
182
+ .error=${this.errors[f.attribute] ?? null}
183
+ .dense=${this.dense}
184
+ .dynamicOptions=${this.dynamicOptions}
185
+ ></aq-mcp-field>`);
186
+ }
187
+ renderDenseBody(applied) {
188
+ const { primary, defaults, toggles } = this.zones(applied);
189
+ const twoUp = this.columns === 2;
190
+ return html `
191
+ ${primary.length ? html `<div class="aq-mcp-zone aq-mcp-zone--primary">${this.renderFields(primary)}</div>` : ''}
192
+ ${defaults.length
193
+ ? html `
194
+ <div class="aq-mcp-zone__rule" role="separator"></div>
195
+ <div class="aq-mcp-zone aq-mcp-zone--defaults ${twoUp ? 'aq-mcp-zone--two-up' : ''}">
196
+ ${this.renderFields(defaults)}
197
+ </div>
198
+ `
199
+ : ''}
200
+ ${toggles.length ? html `<div class="aq-mcp-zone aq-mcp-zone--toggles">${this.renderFields(toggles)}</div>` : ''}
201
+ `;
202
+ }
203
+ render() {
204
+ const s = this.section;
205
+ if (!s)
206
+ return html ``;
207
+ const applied = (s.fields ?? []).filter((f) => !f.hidden && fieldApplies(f, this.values) && !hasNothingToShow(f, this.values));
208
+ const showSave = sectionHasEditableFields(s, this.values);
209
+ return html `
210
+ <div
211
+ class="aq-mcp-section ${this.compact ? 'aq-mcp-section--compact' : ''} ${this.dense
212
+ ? 'aq-mcp-section--dense'
213
+ : ''}"
214
+ data-section=${s.key}
215
+ >
216
+ ${this.renderHeader()}
217
+ <div class="aq-mcp-section__body ${this.dense ? 'aq-mcp-section__body--dense' : ''}">
218
+ ${this.dense ? this.renderDenseBody(applied) : this.renderFields(applied)}
219
+ </div>
220
+ ${this.renderFooter(showSave)}
221
+ </div>
222
+ `;
223
+ }
224
+ };
225
+ __decorate([
226
+ property({ attribute: false }),
227
+ __metadata("design:type", Object)
228
+ ], AqMcpSection.prototype, "section", void 0);
229
+ __decorate([
230
+ property({ attribute: false }),
231
+ __metadata("design:type", Object)
232
+ ], AqMcpSection.prototype, "values", void 0);
233
+ __decorate([
234
+ property({ attribute: false }),
235
+ __metadata("design:type", Object)
236
+ ], AqMcpSection.prototype, "errors", void 0);
237
+ __decorate([
238
+ property({ attribute: false }),
239
+ __metadata("design:type", Object)
240
+ ], AqMcpSection.prototype, "dynamicOptions", void 0);
241
+ __decorate([
242
+ property({ type: Boolean }),
243
+ __metadata("design:type", Object)
244
+ ], AqMcpSection.prototype, "showFooter", void 0);
245
+ __decorate([
246
+ property({ type: Boolean }),
247
+ __metadata("design:type", Object)
248
+ ], AqMcpSection.prototype, "compact", void 0);
249
+ __decorate([
250
+ property({ type: Boolean }),
251
+ __metadata("design:type", Object)
252
+ ], AqMcpSection.prototype, "dense", void 0);
253
+ __decorate([
254
+ property({ type: Number }),
255
+ __metadata("design:type", Number)
256
+ ], AqMcpSection.prototype, "columns", void 0);
257
+ AqMcpSection = AqMcpSection_1 = __decorate([
258
+ customElement('aq-mcp-section')
259
+ ], AqMcpSection);
260
+ export { AqMcpSection };
261
+ //# sourceMappingURL=aq-mcp-section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aq-mcp-section.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/elements/aq-mcp-section.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Section-completeness engine — the derived data behind `sectionsLayout:
3
+ * 'accordion'`'s per-card attention state. A collapsed section shows nothing
4
+ * of its contents, so something has to answer "is there anything left to do in
5
+ * here" without opening it; that answer is currently rendered as the card's
6
+ * tinted edge rather than as header text.
7
+ *
8
+ * Deliberately distinct from `<aq-mcp-config>`'s own `sectionHasError()`,
9
+ * which reads recorded validation errors: a required field the user simply
10
+ * hasn't reached yet is INCOMPLETE, not INVALID. This module reports
11
+ * completeness (has a value been supplied), the error machinery keeps
12
+ * reporting validity (is the supplied value acceptable) — they answer
13
+ * different questions and neither replaces the other.
14
+ */
15
+ import type { Section, Values } from '../types.js';
16
+ /** What the accordion header renders from. */
17
+ export interface SectionStatus {
18
+ /** Applicable, non-hidden, value-bearing fields. */
19
+ fieldCount: number;
20
+ requiredEmpty: number;
21
+ optionalEmpty: number;
22
+ /**
23
+ * - `incomplete` — at least one required field has no value (needs attention)
24
+ * - `complete` — every counted field has a value
25
+ * - `required-set` — every REQUIRED field has a value, some optional ones don't
26
+ * - `neutral` — nothing to count (a section of buttons/reports only)
27
+ */
28
+ state: 'complete' | 'required-set' | 'incomplete' | 'neutral';
29
+ }
30
+ /** Summarise one section against the current values. Pure — no element state. */
31
+ export declare function sectionStatus(section: Section, values: Values): SectionStatus;
@@ -0,0 +1,44 @@
1
+ import { fieldApplies } from './values.js';
2
+ import { isEmpty } from './validate.js';
3
+ import { WRITE_ONCE_UNCHANGED } from './submit.js';
4
+ /**
5
+ * A field counts toward a section's status when it can actually hold a value
6
+ * the user is expected to supply: same "is this in play" rule
7
+ * `<aq-mcp-config>`'s `fieldNeedsValidation()` uses, so the chip can never
8
+ * claim a section is complete while a field it skipped is failing validation.
9
+ * `datatype: 'none'` (buttons, `test-connection-report`, `permissions-report`,
10
+ * `properties-table`) is excluded — those never hold a submitted value, and
11
+ * counting them would inflate "N fields" with things the user can't fill in.
12
+ */
13
+ function counts(field, values) {
14
+ return field.datatype !== 'none' && !field.hidden && fieldApplies(field, values);
15
+ }
16
+ /**
17
+ * A stored write-once secret (§7) is SET, even though its recorded value is
18
+ * only the sentinel rather than the real secret — the whole point of the
19
+ * sentinel is "the host already has this on record." Treating it as empty
20
+ * would flag every saved credentials section as incomplete forever.
21
+ */
22
+ function hasValue(value) {
23
+ return value === WRITE_ONCE_UNCHANGED || !isEmpty(value);
24
+ }
25
+ /** Summarise one section against the current values. Pure — no element state. */
26
+ export function sectionStatus(section, values) {
27
+ let fieldCount = 0;
28
+ let requiredEmpty = 0;
29
+ let optionalEmpty = 0;
30
+ for (const field of section?.fields ?? []) {
31
+ if (!counts(field, values))
32
+ continue;
33
+ fieldCount += 1;
34
+ if (hasValue(values[field.attribute]))
35
+ continue;
36
+ if (field.isRequired)
37
+ requiredEmpty += 1;
38
+ else
39
+ optionalEmpty += 1;
40
+ }
41
+ const state = fieldCount === 0 ? 'neutral' : requiredEmpty > 0 ? 'incomplete' : optionalEmpty === 0 ? 'complete' : 'required-set';
42
+ return { fieldCount, requiredEmpty, optionalEmpty, state };
43
+ }
44
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/engine/status.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAiBnD;;;;;;;;GAQG;AACH,SAAS,MAAM,CAAC,KAAY,EAAE,MAAc;IAC1C,OAAO,KAAK,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACnF,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,oBAAoB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,MAAc;IAC5D,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC;YAAE,SAAS;QACrC,UAAU,IAAI,CAAC,CAAC;QAChB,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAAE,SAAS;QAChD,IAAI,KAAK,CAAC,UAAU;YAAE,aAAa,IAAI,CAAC,CAAC;;YACpC,aAAa,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,KAAK,GACT,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC;IAEtH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Submit assembly (§14) + save-target resolution (§13).
3
+ *
4
+ * Values keyed by `attribute`, only for fields that applied. `datatype:"none"`
5
+ * and hidden fields submit nothing (hidden omitted = unchanged). A blank
6
+ * password/`writeOnce` field is omitted (= keep current). Numbers are coerced.
7
+ * `submitTool` (per section) vs the platform default is carried on the payload;
8
+ * the library never calls the tool itself.
9
+ */
10
+ import type { Section, Values } from '../types.js';
11
+ /**
12
+ * Sentinel a host seeds as a `writeOnce` field's value to mean "a value is
13
+ * stored, unchanged". The field renders masked + "Replace" (§7) and submit omits
14
+ * it. Once the user clicks Replace and types, the real value replaces it.
15
+ */
16
+ export declare const WRITE_ONCE_UNCHANGED = "__aq_write_once_unchanged__";
17
+ export interface SubmitPayload {
18
+ sectionKey: string;
19
+ /** Connector tool to call, or null → platform default (`applications_update`) (§13). */
20
+ submitTool: string | null;
21
+ values: Values;
22
+ }
23
+ /** Build the submit payload for one section (§13/§14). */
24
+ export declare function collectSubmit(section: Section, values: Values): SubmitPayload;
25
+ /** True when a section has at least one editable (value-bearing, visible, applied) field. */
26
+ export declare function sectionHasEditableFields(section: Section, values: Values): boolean;
@@ -0,0 +1,37 @@
1
+ import { fieldApplies } from './values.js';
2
+ /**
3
+ * Sentinel a host seeds as a `writeOnce` field's value to mean "a value is
4
+ * stored, unchanged". The field renders masked + "Replace" (§7) and submit omits
5
+ * it. Once the user clicks Replace and types, the real value replaces it.
6
+ */
7
+ export const WRITE_ONCE_UNCHANGED = '__aq_write_once_unchanged__';
8
+ function coerce(field, value) {
9
+ if (field.datatype === 'number') {
10
+ const n = Number(value);
11
+ return Number.isNaN(n) ? value : n;
12
+ }
13
+ return value;
14
+ }
15
+ /** Build the submit payload for one section (§13/§14). */
16
+ export function collectSubmit(section, values) {
17
+ const out = {};
18
+ for (const field of section.fields ?? []) {
19
+ if (field.datatype === 'none' || field.hidden)
20
+ continue;
21
+ if (!fieldApplies(field, values))
22
+ continue;
23
+ const v = values[field.attribute];
24
+ const isSecretEntry = field['ui-component'] === 'password' || field.writeOnce;
25
+ if (isSecretEntry && (v == null || v === '' || v === WRITE_ONCE_UNCHANGED))
26
+ continue; // blank/unchanged
27
+ if (v === undefined)
28
+ continue;
29
+ out[field.attribute] = coerce(field, v);
30
+ }
31
+ return { sectionKey: section.key, submitTool: section.submitTool ?? null, values: out };
32
+ }
33
+ /** True when a section has at least one editable (value-bearing, visible, applied) field. */
34
+ export function sectionHasEditableFields(section, values) {
35
+ return (section.fields ?? []).some((f) => f.datatype !== 'none' && !f.hidden && fieldApplies(f, values));
36
+ }
37
+ //# sourceMappingURL=submit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/engine/submit.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAElE,SAAS,MAAM,CAAC,KAAY,EAAE,KAAc;IAC1C,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AASD,0DAA0D;AAC1D,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,MAAc;IAC5D,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM;YAAE,SAAS;QACxD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC;YAAE,SAAS;QAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,UAAU,IAAI,KAAK,CAAC,SAAS,CAAC;QAC9E,IAAI,aAAa,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,oBAAoB,CAAC;YAAE,SAAS,CAAC,kBAAkB;QACxG,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAC1F,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,wBAAwB,CAAC,OAAgB,EAAE,MAAc;IACvE,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CACrE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Validation engine (bootstrap contract §8). The engine owns the *logic* and
3
+ * returns a message; the Nile control owns *display* (the caller pushes the
4
+ * message into the control's `error`/`errorMessage`) — the split from D-7.
5
+ *
6
+ * Rules: patterns are ECMAScript regex sources, matched against the whole value,
7
+ * AND-combined. Emptiness is governed by `isRequired`, not regex. A regex that
8
+ * fails to compile is a descriptor defect — logged and skipped, never blocking.
9
+ */
10
+ import type { Field } from '../types.js';
11
+ /**
12
+ * "Has this field got a value at all" — the single emptiness notion in this
13
+ * library. Exported (not just used by `validateField`'s `isRequired` check)
14
+ * so `engine/status.ts`'s section-completeness counts mean exactly the same
15
+ * thing the required-check means: a whitespace-only string, `[]` and `{}` are
16
+ * empty; `false` (an off toggle) and a `File` are NOT — they are values a user
17
+ * chose. Two separate emptiness rules would let a section read "Complete"
18
+ * while a field it contains still fails validation.
19
+ */
20
+ export declare function isEmpty(value: unknown): boolean;
21
+ /**
22
+ * Validate a field's value. Returns the message to show, or null when valid.
23
+ */
24
+ export declare function validateField(field: Field, value: unknown): string | null;
@@ -0,0 +1,144 @@
1
+ import { WRITE_ONCE_UNCHANGED } from './submit.js';
2
+ const REQUIRED_MESSAGE = 'This field is required.';
3
+ /**
4
+ * "Has this field got a value at all" — the single emptiness notion in this
5
+ * library. Exported (not just used by `validateField`'s `isRequired` check)
6
+ * so `engine/status.ts`'s section-completeness counts mean exactly the same
7
+ * thing the required-check means: a whitespace-only string, `[]` and `{}` are
8
+ * empty; `false` (an off toggle) and a `File` are NOT — they are values a user
9
+ * chose. Two separate emptiness rules would let a section read "Complete"
10
+ * while a field it contains still fails validation.
11
+ */
12
+ export function isEmpty(value) {
13
+ if (value == null)
14
+ return true;
15
+ if (typeof value === 'string')
16
+ return value.trim() === '';
17
+ if (Array.isArray(value))
18
+ return value.length === 0;
19
+ if (typeof File !== 'undefined' && value instanceof File)
20
+ return false; // a File has no own enumerable keys
21
+ if (typeof value === 'object')
22
+ return Object.keys(value).length === 0;
23
+ return false;
24
+ }
25
+ /** `accept` match against a File's MIME type or filename extension (§8, `file` datatype). */
26
+ function acceptMatches(accept, file) {
27
+ return accept.some((a) => {
28
+ if (a.startsWith('.'))
29
+ return file.name.toLowerCase().endsWith(a.toLowerCase());
30
+ if (a.endsWith('/*'))
31
+ return file.type.startsWith(a.slice(0, -1));
32
+ return file.type === a;
33
+ });
34
+ }
35
+ function validateFile(field, file, msg) {
36
+ // `value` reaching here is only ever SUPPOSED to be a real File — but a stale
37
+ // value from before the field became a file-upload type, or any other bad
38
+ // input the host hands back, is a plain object/string with no `.name`/`.size`.
39
+ // Fail safe (invalid, same as any other malformed value) instead of crashing.
40
+ if (!(typeof File !== 'undefined' && file instanceof File))
41
+ return msg;
42
+ if (field.maxSizeKB != null && file.size > field.maxSizeKB * 1024)
43
+ return msg;
44
+ if (field.accept?.length && !acceptMatches(field.accept, file))
45
+ return msg;
46
+ return null;
47
+ }
48
+ function compile(patterns) {
49
+ const out = [];
50
+ for (const p of patterns ?? []) {
51
+ try {
52
+ out.push(new RegExp(p));
53
+ }
54
+ catch {
55
+ // eslint-disable-next-line no-console
56
+ console.warn(`[mcp-ui-render] invalid regex "${p}": field treated as unvalidated (§8).`);
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+ function allMatch(regexes, s) {
62
+ return regexes.every((r) => r.test(s));
63
+ }
64
+ /**
65
+ * Validate a field's value. Returns the message to show, or null when valid.
66
+ */
67
+ export function validateField(field, value) {
68
+ // A `writeOnce` field's already-stored value is this sentinel string, never
69
+ // the real value (§7) — validating it against the field's own rules is
70
+ // meaningless (it's not what will be submitted) and, for `file` fields,
71
+ // actively crashes: `validateFile` below reads `.name`/`.size` expecting a
72
+ // real `File`, which a plain string doesn't have. Short-circuit before any
73
+ // datatype-specific check ever sees it, same as `<aq-mcp-field>`'s own
74
+ // `isSetWriteOnce()` already treats this sentinel as "stored, don't touch."
75
+ if (value === WRITE_ONCE_UNCHANGED)
76
+ return null;
77
+ const dt = field.datatype;
78
+ if (dt === 'none')
79
+ return null;
80
+ if (isEmpty(value))
81
+ return field.isRequired ? REQUIRED_MESSAGE : null;
82
+ if (dt === 'boolean')
83
+ return null;
84
+ const msg = field.validationmessage ?? 'Invalid value.';
85
+ const regexes = compile(field.validation);
86
+ if (field['ui-component'] === 'file') {
87
+ // Covers both single-file (`datatype: "file"`) and multi-file (`datatype:
88
+ // "array"` + `ui-component: "file"`) — binary content, so `validation`
89
+ // regex doesn't apply; only `minItems`/`maxItems` (multi) + `accept`/`maxSizeKB` (§8).
90
+ const files = Array.isArray(value) ? value : [value];
91
+ if (dt === 'array') {
92
+ if (field.minItems != null && files.length < field.minItems)
93
+ return msg;
94
+ if (field.maxItems != null && files.length > field.maxItems)
95
+ return msg;
96
+ }
97
+ return files.every((f) => validateFile(field, f, msg) === null) ? null : msg;
98
+ }
99
+ switch (dt) {
100
+ case 'string':
101
+ return regexes.length && !allMatch(regexes, String(value)) ? msg : null;
102
+ case 'number': {
103
+ if (regexes.length && !allMatch(regexes, String(value)))
104
+ return msg;
105
+ const n = Number(value);
106
+ if (!Number.isNaN(n)) {
107
+ if (field.min != null && n < field.min)
108
+ return msg;
109
+ if (field.max != null && n > field.max)
110
+ return msg;
111
+ }
112
+ return null;
113
+ }
114
+ case 'array': {
115
+ const arr = Array.isArray(value) ? value : [value];
116
+ if (field.minItems != null && arr.length < field.minItems)
117
+ return msg;
118
+ if (field.maxItems != null && arr.length > field.maxItems)
119
+ return msg;
120
+ if (regexes.length && !arr.every((el) => allMatch(regexes, String(el))))
121
+ return msg;
122
+ return null;
123
+ }
124
+ case 'object': {
125
+ const obj = typeof value === 'object' && value ? value : {};
126
+ for (const k of field.requiredKeys ?? []) {
127
+ if (!(k in obj))
128
+ return msg;
129
+ }
130
+ if (regexes.length && !Object.values(obj).every((v) => allMatch(regexes, String(v))))
131
+ return msg;
132
+ return null;
133
+ }
134
+ default: {
135
+ // Unknown datatype → treat as string, warn if required (§9.2).
136
+ if (field.isRequired) {
137
+ // eslint-disable-next-line no-console
138
+ console.warn(`[mcp-ui-render] unknown datatype "${String(dt)}" on required field "${field.attribute}": validating as string (§9.2).`);
139
+ }
140
+ return regexes.length && !allMatch(regexes, String(value)) ? msg : null;
141
+ }
142
+ }
143
+ }
144
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/engine/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Value-model helpers: dependency applicability (§12) and default seeding.
3
+ * Unknown keys already survive because `Values` is an open record and updates
4
+ * are shallow merges — nothing here drops properties it doesn't recognise (§9.4).
5
+ */
6
+ import type { Descriptor, Field, Values } from '../types.js';
7
+ export declare function asArray(v: string | string[] | undefined): string[];
8
+ /**
9
+ * A field applies unless it declares at least one dependency condition — either
10
+ * `dependencyAttribute`/`dependencyValue` (single attribute) or `dependsOn` (a list
11
+ * of attribute/value conditions, §9.7) — and NONE of them currently match. With
12
+ * both present, or multiple `dependsOn` entries, they're OR'd together: any one
13
+ * condition matching is enough (e.g. an FTP-server picker relevant when EITHER
14
+ * `sourceConnectionType === 'directConnection'` OR `storageType === 'sftpStorage'`).
15
+ * A field with no dependency declared at all always applies (§12). Non-applying
16
+ * fields are skipped and their stored value dropped by the caller.
17
+ */
18
+ export declare function fieldApplies(field: Field, values: Values): boolean;
19
+ /** Seed `defaultValue` for applied, value-bearing fields that have no value yet (§12). */
20
+ export declare function seedDefaults(descriptor: Descriptor, values: Values): Values;