@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.
- package/CHANGELOG.md +437 -0
- package/CONFIGURATION.md +178 -0
- package/LICENSE +21 -0
- package/README.md +237 -0
- package/RELEASE-NOTES.md +77 -0
- package/TEST-PLAN.md +72 -0
- package/USAGE.md +100 -0
- package/package.json +52 -0
- package/src/controls.d.ts +1 -0
- package/src/controls.js +572 -0
- package/src/controls.js.map +1 -0
- package/src/elements/aq-mcp-config.d.ts +289 -0
- package/src/elements/aq-mcp-config.js +673 -0
- package/src/elements/aq-mcp-config.js.map +1 -0
- package/src/elements/aq-mcp-field.d.ts +92 -0
- package/src/elements/aq-mcp-field.js +275 -0
- package/src/elements/aq-mcp-field.js.map +1 -0
- package/src/elements/aq-mcp-section.d.ts +101 -0
- package/src/elements/aq-mcp-section.js +261 -0
- package/src/elements/aq-mcp-section.js.map +1 -0
- package/src/engine/status.d.ts +31 -0
- package/src/engine/status.js +44 -0
- package/src/engine/status.js.map +1 -0
- package/src/engine/submit.d.ts +26 -0
- package/src/engine/submit.js +37 -0
- package/src/engine/submit.js.map +1 -0
- package/src/engine/validate.d.ts +24 -0
- package/src/engine/validate.js +144 -0
- package/src/engine/validate.js.map +1 -0
- package/src/engine/values.d.ts +20 -0
- package/src/engine/values.js +42 -0
- package/src/engine/values.js.map +1 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/mcp-ui-render.css +811 -0
- package/src/registry.d.ts +63 -0
- package/src/registry.js +39 -0
- package/src/registry.js.map +1 -0
- package/src/types.d.ts +239 -0
- package/src/types.js +8 -0
- package/src/types.js.map +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# Changelog — pre-npm development history (Verdaccio)
|
|
2
|
+
|
|
3
|
+
> **This file is history, not the release record.** Everything below documents the package's
|
|
4
|
+
> development against the **private Verdaccio registry**, where it reached `0.5.4` published and
|
|
5
|
+
> `0.6.0` in-repo. None of these versions exist on public npm.
|
|
6
|
+
>
|
|
7
|
+
> **For published releases, see [`RELEASE-NOTES.md`](./RELEASE-NOTES.md)** — the public npm line
|
|
8
|
+
> starts at `0.0.1`, which is the same code as the internal `0.6.0` build.
|
|
9
|
+
>
|
|
10
|
+
> It is kept rather than deleted because the reasoning in these entries — why a control behaves
|
|
11
|
+
> the way it does, what an interim version got wrong — is the only record of those decisions, and
|
|
12
|
+
> that context outlives the registry it was written against.
|
|
13
|
+
|
|
14
|
+
All notable changes to `@aquera/mcp-ui-render` are documented here.
|
|
15
|
+
Format loosely follows [Keep a Changelog](https://keepachangelog.com/); this package
|
|
16
|
+
follows semver (MAJOR.MINOR.PATCH) once past 0.x.
|
|
17
|
+
|
|
18
|
+
## [0.6.0] - 2026-08-26
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`sectionsLayout: 'accordion'` — a third section arrangement.** Alongside `'stacked'` (a card
|
|
23
|
+
per section) and `'tabs'`, sections can now render as a single-open stack of `nile-accordion`
|
|
24
|
+
cards. The point is the *collapsed* state: a section that isn't open still reports what's inside
|
|
25
|
+
it, so a multi-section form is scannable without opening every card.
|
|
26
|
+
- **Header.** An icon tile, the section label with a describing line under it, and a completion
|
|
27
|
+
status beside the chevron — **✓ Complete** or **N required left** — so a collapsed section says
|
|
28
|
+
both what it is and whether it still needs work. Status is plain text with a mark rather than a
|
|
29
|
+
filled pill: it is a readout, not a control, and colour is never the only signal.
|
|
30
|
+
- **The header writes no connector-describing text of its own.** `section.label` and
|
|
31
|
+
`section.authType` are rendered verbatim — the label as the title, the authType as the same
|
|
32
|
+
`nile-badge` the stacked and tabs layouts already use. Nothing is composed around them and
|
|
33
|
+
nothing is inferred from a key being absent. An interim version rendered
|
|
34
|
+
"Basic authentication · saved by the platform" under the title, which read as the connector
|
|
35
|
+
describing itself but wasn't: only "Basic" came from the descriptor, and "saved by the
|
|
36
|
+
platform" was the renderer turning a MISSING `submitTool` into a sentence. The `bootstrap`
|
|
37
|
+
format carries no prose field and its shape is fixed, so rather than add a key or invent copy,
|
|
38
|
+
the header now shows only values the descriptor supplied.
|
|
39
|
+
- **`sectionIcons`** (a host map keyed by section key) sets the header tile, over a built-in
|
|
40
|
+
default for the contract's own `authorization` and `tenantAttributes` keys and a generic
|
|
41
|
+
fallback. The override is host-side because the format carries no icon field — and because
|
|
42
|
+
iconography is the application's decision, not the connector's.
|
|
43
|
+
- **Dense field layout.** The open card's body is a grid of label-**above**-control cells rather
|
|
44
|
+
than the label-left/control-right rows the other layouts use, with each field's `help` as hint
|
|
45
|
+
text beneath the control — the same `renderHelp()` (and the same "View more/less" truncation)
|
|
46
|
+
every other layout uses, only repositioned, so one description reads identically everywhere.
|
|
47
|
+
An empty field shows "Not set" as its placeholder, and a boolean renders as a labelled toggle
|
|
48
|
+
pill. Booleans keep their declared `order` — styled differently, not reordered.
|
|
49
|
+
- **Field zoning.** The open card splits into three: fields with no `defaultValue` (what you must
|
|
50
|
+
actually decide) full width; fields that already carry a working default grouped two-up under a
|
|
51
|
+
"defaults you can tune" rule; and booleans last, as full-width rows carrying their own
|
|
52
|
+
description. This DOES reorder relative to the descriptor — the only layout that does — because
|
|
53
|
+
an "everything you must set" block reads better than defaults interleaved with required fields.
|
|
54
|
+
Order is preserved within each zone.
|
|
55
|
+
- **The required marker is now conditional.** `Needs a value` shows only while a required field is
|
|
56
|
+
still empty, instead of an asterisk on every required field forever. A permanent mark on
|
|
57
|
+
finished fields is noise that hides the ones that still mean something; this makes the marker a
|
|
58
|
+
to-do list, matching what the header's "N required left" counts.
|
|
59
|
+
- **A stored `writeOnce` secret reads as a state**, not a masked value: a lock and "Saved and
|
|
60
|
+
encrypted" rather than a row of dots, since the host holds the real secret and this element
|
|
61
|
+
never sees it (§7) — dots imply there is something here to reveal.
|
|
62
|
+
- **Dense help is never truncated.** The `View more` threshold is tuned for the stacked layout's
|
|
63
|
+
narrow label column; as a full-width hint line the same text already fits.
|
|
64
|
+
- **`accordionColumns`** (`1 | 2`, default `2`) sets how many columns the defaults group uses. It is a
|
|
65
|
+
ceiling, not a fixed count: at `2` the grid still collapses to one column when its CONTAINER
|
|
66
|
+
is too narrow (not the viewport — the same form is 1180px wide on its own page and ~400px
|
|
67
|
+
beside a docked panel at an identical viewport), so `1` means "never two".
|
|
68
|
+
- **`accordionOpenSection`** (default `null`) picks the initially-expanded section: unset, the
|
|
69
|
+
library opens the first section still missing a required value, else the first visible one — so
|
|
70
|
+
the form opens on the part that needs work. A section `key` pins it; `''` starts all collapsed.
|
|
71
|
+
Only re-evaluated on a descriptor change, never on a value edit, so the card being typed in
|
|
72
|
+
never slams shut.
|
|
73
|
+
- **`accordionShowFooters`** (default **`false`**, unlike the other layouts) — an accordion is a
|
|
74
|
+
whole-form overview, and the host normally drives one submit via `validateAll()`. Set it true
|
|
75
|
+
for per-section "Save `<label>`" buttons here too; `showSectionFooters: false` still wins.
|
|
76
|
+
- An unrecognised `sectionsLayout` value now falls back to `'stacked'` rather than rendering an
|
|
77
|
+
empty form.
|
|
78
|
+
|
|
79
|
+
- **`sectionStatus()`** (`engine/status.ts`, exported) — the pure function behind that attention
|
|
80
|
+
state, usable standalone by a host that wants the same "is this section done" summary elsewhere. It counts applicable, non-hidden, value-bearing fields
|
|
81
|
+
(§12/§7; `datatype: "none"` excluded — buttons and reports hold no value the user supplies) and
|
|
82
|
+
treats a stored write-once secret (§7) as set.
|
|
83
|
+
|
|
84
|
+
It is deliberately **not** the same question as the existing per-tab error dot: a required field
|
|
85
|
+
the user simply hasn't reached yet is *incomplete*, not *invalid*. `sectionStatus` reports
|
|
86
|
+
completeness; the error machinery keeps reporting validity, and neither replaces the other.
|
|
87
|
+
|
|
88
|
+
- **`isEmpty()`** is now exported from `engine/validate.js`. It was already the private rule behind
|
|
89
|
+
`isRequired`; exporting it is what lets `sectionStatus` mean exactly the same thing by "empty" —
|
|
90
|
+
two independent emptiness notions would let a section read "Complete" while a field inside it
|
|
91
|
+
still failed validation.
|
|
92
|
+
|
|
93
|
+
- **`ControlContext.dense`** — an optional, purely advisory flag telling a control its row is being
|
|
94
|
+
laid out label-above-control in a narrow grid cell. Every built-in renders correctly either way
|
|
95
|
+
and a custom control that ignores it is not broken; the built-ins use it for one thing, showing
|
|
96
|
+
"Not set" as the placeholder of an empty field that has no `defaultValue` to advertise instead.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **A field's help tooltip no longer collapses the section it lives in.** `nile-tooltip` emits
|
|
101
|
+
`nile-show`/`nile-hide` — the same event names `nile-accordion` uses — and every Nile component
|
|
102
|
+
emits `bubbles: true, composed: true` (`internal/nile-element.ts`). The accordion's own handlers
|
|
103
|
+
therefore saw a tooltip opening as "open this section" and its dismissal as "close it", so the
|
|
104
|
+
card slammed shut under the user's cursor. The handlers now ignore any disclosure event whose
|
|
105
|
+
target isn't the accordion itself, which also covers any future nested Nile component reusing
|
|
106
|
+
those names.
|
|
107
|
+
|
|
108
|
+
### Notes
|
|
109
|
+
|
|
110
|
+
- `nile-accordion` is registered by `@aquera/nile-elements`' root bundle (what a host imports), but
|
|
111
|
+
the package ships no per-element export subpath for it — the same gap the browser suite already
|
|
112
|
+
documents for `nile-file-upload`. Hosts importing the whole package are unaffected.
|
|
113
|
+
- No change to `types.ts`: layout stays a host-side element property and never enters the
|
|
114
|
+
`bootstrap` descriptor, same as `'tabs'`.
|
|
115
|
+
|
|
116
|
+
## [0.5.5] - 2026-08-08
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- **`datatype: "object"` now has a real editable control** — `validateField` has always had
|
|
121
|
+
proper object validation (`requiredKeys`, per-value regex, §8), but no `ui-component` actually
|
|
122
|
+
rendered an object usably: `input`/`textarea` both did `String(value)`, which for a real object
|
|
123
|
+
produces the literal text `"[object Object]"` — not editable, not even legible. `input` and
|
|
124
|
+
`textarea` now display an object value as pretty-printed JSON, and parse the typed text back
|
|
125
|
+
into a real object on input; while the JSON is mid-typing and not yet valid/complete (the
|
|
126
|
+
normal state for anything beyond a bare `{}`), the raw text is kept as an interim value instead
|
|
127
|
+
of being dropped or corrupted — `validateField`'s object branch already treats a non-object
|
|
128
|
+
value as an empty object rather than crashing, so this degrades to an ordinary "missing
|
|
129
|
+
required key(s)" validation message, never a throw, until the JSON is valid again.
|
|
130
|
+
`DATATYPE_DEFAULT['object']` changed from `input` to `textarea` (a single line is a poor fit
|
|
131
|
+
for JSON; `textarea` is also what real descriptors already pair `object` with, e.g.
|
|
132
|
+
`flatfile-mcp-server`'s `tenantAttributes.schemas`). Found — like the `0.5.4` defaults fix —
|
|
133
|
+
while validating a real `flatfile-mcp-server` bootstrap descriptor end to end; not currently
|
|
134
|
+
exercised by any hidden field in that descriptor, but a real gap for any connector that ever
|
|
135
|
+
ships a non-hidden `object` field.
|
|
136
|
+
|
|
137
|
+
## [0.5.4] - 2026-08-08
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- **A dependent field's own `defaultValue` was never seeded when it became newly applicable
|
|
142
|
+
mid-session — only at initial descriptor load.** `<aq-mcp-config>`'s `willUpdate` only called
|
|
143
|
+
`seedDefaults()` when the `descriptor` property itself changed. A field gated behind
|
|
144
|
+
`dependencyAttribute`/`dependencyValue` (or `dependsOn`) that's inapplicable at first render —
|
|
145
|
+
e.g. a "Storage Type" radio that only applies once "Source Connection Type" is set to "Flat
|
|
146
|
+
File" — becomes applicable via a `values` change (the user picking something elsewhere), not a
|
|
147
|
+
`descriptor` change, so its `defaultValue` was silently never seeded: the field rendered with
|
|
148
|
+
nothing selected despite the descriptor declaring a default, and — worse, for a non-required
|
|
149
|
+
field — that default would never be submitted at all, since `collectSubmit` only submits keys
|
|
150
|
+
already present in `values`. Found while validating a real `flatfile-mcp-server` bootstrap
|
|
151
|
+
descriptor end to end. Fixed by re-running `seedDefaults` on a `values` change too, guarded by a
|
|
152
|
+
key-count comparison (`seedDefaults` only ever fills in a currently-`undefined` attribute, so a
|
|
153
|
+
key-count mismatch is a cheap, correct signal that seeding actually added something) —
|
|
154
|
+
reassigning `this.values` only when seeding truly changed it avoids re-triggering `willUpdate`
|
|
155
|
+
forever.
|
|
156
|
+
|
|
157
|
+
## [0.5.3] - 2026-08-08
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
|
|
161
|
+
- **`.aq-mcp-section`, `.aq-mcp-tabs`, and the selected permissions-report filter chip kept a
|
|
162
|
+
white background under the dark theme.** All three used `--ng-color-base-white`, a raw Nile
|
|
163
|
+
palette token that's `#FFFFFF` unconditionally in both light AND dark (it never flips) —
|
|
164
|
+
the theme-aware surface token is `--ng-colors-bg-primary`, which resolves to
|
|
165
|
+
`--ng-color-base-white` in light and `--ng-color-graydarkmode-950` in dark. Every text token in
|
|
166
|
+
the sheet was already correct (`--ng-colors-text-primary-900`/`-secondary-700`/`-tertiary-600`
|
|
167
|
+
all flip), so under dark mode section headings and help text turned near-white against a card
|
|
168
|
+
that stayed white — headings effectively invisible, `nile-input` controls rendering as black
|
|
169
|
+
boxes on a white card. `.aq-mcp-section__footer` uses `--ng-colors-bg-secondary` and DOES flip,
|
|
170
|
+
so the footer strip went dark inside a still-white card. Fixed by swapping all three to
|
|
171
|
+
`--ng-colors-bg-primary` (light mode unaffected — it resolves to the same white there).
|
|
172
|
+
- **The selected permissions-report filter chip's text color was never theme-aware at all.**
|
|
173
|
+
`--ng-colors-brand-600` isn't a real Nile token (checked every `nxtgen-*.css` shipped) — it
|
|
174
|
+
silently fell back to the hardcoded `#155eef` in both themes, every time. Swapped for
|
|
175
|
+
`--ng-componentcolors-utility-brand-600`, the real semantic token, which resolves to the exact
|
|
176
|
+
same `#155EEF` in light mode (zero visual change there) and to a lighter `#528BFF` in dark mode
|
|
177
|
+
— the old `#155eef`-on-`--ng-colors-bg-primary` combination in dark mode measured ~3.6:1
|
|
178
|
+
contrast, borderline-failing for text; the fixed token measures noticeably better.
|
|
179
|
+
**Known, left as-is:** `.aq-mcp-field__help-toggle`'s "View more"/"View less" link has the same
|
|
180
|
+
nonexistent `--ng-colors-brand-600` token, falling back to `#7f56d9` — Aquera's actual brand
|
|
181
|
+
purple, not Nile's blue. No theme-flipping purple token exists in Nile (every "brand" text
|
|
182
|
+
token there resolves to the blue `bluedark` family), and the literal purple already reads
|
|
183
|
+
~3.9:1 against the new dark surface, so swapping it would trade a working purple for an
|
|
184
|
+
unintended blue rather than fix anything — left alone.
|
|
185
|
+
|
|
186
|
+
## [0.5.2] - 2026-08-07
|
|
187
|
+
|
|
188
|
+
### Fixed
|
|
189
|
+
|
|
190
|
+
- **`validateFile` crashed on any non-`File` value, not just the `WRITE_ONCE_UNCHANGED`
|
|
191
|
+
sentinel fixed in `0.5.1`.** That fix stopped the sentinel specifically from reaching
|
|
192
|
+
`validateFile`, but `validateFile`/`acceptMatches` still unconditionally assumed
|
|
193
|
+
`value` was a real `File` — any OTHER non-`File` value (stale data left over from
|
|
194
|
+
before a field became a `file`-upload type, or any other malformed value a host
|
|
195
|
+
hands back) still crashed the same way: `file.name.toLowerCase()` on a value with no
|
|
196
|
+
`.name`. Fixed by guarding `validateFile` itself with an `instanceof File` check —
|
|
197
|
+
fails safe (returns the field's validation message, i.e. "invalid") instead of
|
|
198
|
+
throwing, for ANY non-`File` value, not just the one sentinel string. This is the
|
|
199
|
+
correct chokepoint: every `file`-datatype value flows through `validateFile`
|
|
200
|
+
regardless of how it got there, so this closes the crash for good rather than
|
|
201
|
+
special-casing another known-bad value.
|
|
202
|
+
|
|
203
|
+
## [0.5.1] - 2026-08-07
|
|
204
|
+
|
|
205
|
+
### Fixed
|
|
206
|
+
|
|
207
|
+
- **`validateField` crashed on a stored `writeOnce` value for `file` fields, silently killing the whole submit.**
|
|
208
|
+
A `writeOnce` field's stored value is the `WRITE_ONCE_UNCHANGED` sentinel string (§7), never the real
|
|
209
|
+
value — `validateField` never checked for it before running datatype-specific checks. For a `string`
|
|
210
|
+
field this "worked" by accident (`String(sentinel)` against the field's own regex is harmless, if
|
|
211
|
+
meaningless). For a `file` field it crashed for real: the `file`-control branch reads `value.name`/
|
|
212
|
+
`value.size` off what it assumes is a real `File`, and a plain string has neither — `TypeError` thrown
|
|
213
|
+
mid-`computeErrors()`, which both `validateSection` (section Save) and `AqMcpConfig.validateAll()` call
|
|
214
|
+
over *every* field in the section, not just the one being edited. Net effect: any connector with a
|
|
215
|
+
stored file-type credential field (e.g. ADP's client certificate) was unsaveable — editing ANY other
|
|
216
|
+
field in that section threw before ever emitting `aq-mcp-submit`, no request ever sent, Save stuck
|
|
217
|
+
indefinitely. Fixed by short-circuiting `validateField` to `null` the moment `value === WRITE_ONCE_UNCHANGED`,
|
|
218
|
+
before any datatype-specific check runs — the same "sentinel means stored, don't touch" rule
|
|
219
|
+
`<aq-mcp-field>`'s own `isSetWriteOnce()` already applies on the display side.
|
|
220
|
+
|
|
221
|
+
## [0.5.0] - 2026-08-06
|
|
222
|
+
|
|
223
|
+
### Added
|
|
224
|
+
|
|
225
|
+
- **`radio` control** — a new built-in `ui-component`, mapped to `nile-radio-group`/
|
|
226
|
+
`nile-radio` (previously an unregistered `ui-component` silently fell back to the
|
|
227
|
+
datatype default, e.g. `input` for `string` — no radio buttons, no options shown,
|
|
228
|
+
no warning). Needs `options` like `select` does; an empty list logs the same
|
|
229
|
+
"descriptor defect (§15)" warning and renders disabled. Gets the same accessible-name
|
|
230
|
+
wiring as `input`/`textarea`/`password`/`select`: the field's label is passed into
|
|
231
|
+
Nile's own `label` property (real `aria-labelledby` association on the group's
|
|
232
|
+
`<fieldset>`), then visually hidden via `::part(form-control-label)` since this
|
|
233
|
+
library already shows the label in `<aq-mcp-field>`'s own left column.
|
|
234
|
+
- **`select`/`radio` accept `{ value, label }` options**, alongside the original plain
|
|
235
|
+
primitive form (`value === display text`) — e.g. `{ value: 'sftpStorage', label: 'SFTP
|
|
236
|
+
Storage' }` shows "SFTP Storage" to the user while still emitting/submitting the raw
|
|
237
|
+
code `sftpStorage`. Fully backward compatible: a plain string/number option renders
|
|
238
|
+
exactly as before.
|
|
239
|
+
- **`dynamicOptions` property + `aq-mcp-options-request` event (§9.6)** — a way to feed a
|
|
240
|
+
`select`/`radio` field's `options` from a host-executed tool call (e.g. `listFtpServers`),
|
|
241
|
+
since this library still performs no network I/O itself (D-4). A field with `tool` set and
|
|
242
|
+
no options yet emits `aq-mcp-options-request` (`{ attribute, tool, args }`) once it's
|
|
243
|
+
actually applicable (respects `dependencyAttribute`) and at most once per attribute ever;
|
|
244
|
+
the host calls the tool and sets the result into `<aq-mcp-config>.dynamicOptions` (keyed by
|
|
245
|
+
attribute), which takes priority over the field's own static `options`. A field pending a
|
|
246
|
+
fetch renders disabled WITHOUT the §15 "descriptor defect" warning — that warning is now
|
|
247
|
+
reserved for a field with no options and no `tool` either, i.e. genuinely no way to ever get
|
|
248
|
+
any.
|
|
249
|
+
- **`properties-table` control** — a new built-in `ui-component`, `datatype: "none"` (never
|
|
250
|
+
validated/submitted, same contract as `test-connection-report`). Renders a read-only
|
|
251
|
+
Property/Value `<table>` from a `PropertiesTableRow[]` list — for surfacing a handful of
|
|
252
|
+
attributes' values (typically read-only/hidden tenant attributes worth seeing but not worth
|
|
253
|
+
exposing as disabled form fields) without duplicating them into the editable form. Two ways
|
|
254
|
+
to supply the rows, same priority `select`/`radio` already have between `options` and
|
|
255
|
+
`dynamicOptions`: **static**, declared directly on the field as `field.rows` (for data the
|
|
256
|
+
connector already knows when it builds the `bootstrap` response — no host computation
|
|
257
|
+
needed), or **dynamic**, host-supplied via `values[attribute]` (wins over `field.rows` when
|
|
258
|
+
both are present). Formats `null`/`undefined` as an em dash, an array by joining with commas,
|
|
259
|
+
and truncates a long value with an ellipsis (the full value stays available via the cell's
|
|
260
|
+
`title`). Not mapped to a Nile element — Nile has no read-only data-table primitive this
|
|
261
|
+
simple case needs. A plain (non-array) object value is `JSON.stringify`'d rather than
|
|
262
|
+
`String()`'d (so it never renders the literal `[object Object]`), and property names over 24
|
|
263
|
+
characters truncate with an ellipsis + hover `title`, same treatment as values.
|
|
264
|
+
- **`Field.dependsOn?: { attribute: string; value: string | string[] }[]`** — a compound,
|
|
265
|
+
multi-attribute OR dependency, alongside the existing single-attribute
|
|
266
|
+
`dependencyAttribute`/`dependencyValue`. A field applies if `dependencyAttribute` matches OR
|
|
267
|
+
any `dependsOn` entry matches (all OR'd together, never AND'd) — for a field relevant when
|
|
268
|
+
either of two DIFFERENT attributes hits a value, which the single-attribute form can't
|
|
269
|
+
express. Fully backward compatible: a field using only `dependencyAttribute` behaves exactly
|
|
270
|
+
as before.
|
|
271
|
+
- **Collapsible "View more"/"View less" help text** (`<aq-mcp-field>`) — `field.help` over 100
|
|
272
|
+
characters now renders truncated with a toggle instead of always showing in full; help at or
|
|
273
|
+
under the threshold is unaffected. Purely a display convenience with local component state,
|
|
274
|
+
no `Field`/`Values` model change.
|
|
275
|
+
- **`Field.actions` — a multi-action toolbar for a control, alongside the existing single-action
|
|
276
|
+
`button` control.** `properties-table` now renders its own card header (title from
|
|
277
|
+
`field.label`, subtitle from `field.help`, and a right-aligned toolbar built from
|
|
278
|
+
`field.actions`) instead of relying on `<aq-mcp-field>`'s usual label-left/control-right split
|
|
279
|
+
— matching the live reference's "Generated Configuration" card exactly (a download icon
|
|
280
|
+
button + an "Edit Configuration" button, each independently wired to its own tool). An
|
|
281
|
+
`actions` entry with `icon` set renders as an icon-only `nile-icon-button`; without `icon`, a
|
|
282
|
+
labeled `nile-button`. Clicking one calls the control's `onAction` with that specific
|
|
283
|
+
`FieldAction`, and `aq-mcp-action`'s event detail now includes `action` (undefined for the
|
|
284
|
+
classic single-`button` case) so a host can tell which of several actions fired. Each action
|
|
285
|
+
carries its own `tool`/`args`/`confirm`, independent of the field's own (still supported)
|
|
286
|
+
top-level `tool`/`args`/`confirm`.
|
|
287
|
+
- **`properties-table` is now a `FULL_WIDTH_CONTROLS` entry** (`<aq-mcp-field>`) — it spans the
|
|
288
|
+
whole field row instead of sitting in the standard two-column label-left/control-right split,
|
|
289
|
+
since it now renders its own title/subtitle/toolbar. Purely a layout change; `field.rows` /
|
|
290
|
+
`values[attribute]` / empty-value behavior is unchanged, and the field's label still always
|
|
291
|
+
renders somewhere (now in the control's own header) even when there are no rows yet.
|
|
292
|
+
- **`permissions-report` control** — a new `datatype: "none"` read-only control, distinct from
|
|
293
|
+
`test-connection-report`: that one models a list of PASS/FAIL endpoint probes, this one models
|
|
294
|
+
a scope/permission GRANT table (a validation banner + a running "N Granted" count + an
|
|
295
|
+
All/Success filter + a Resource Type/Permission Type/Access table) — matching the live
|
|
296
|
+
reference's own "Credentials" tab Test Connection result exactly. The granted count is always
|
|
297
|
+
derived from the current `grants` array (never a separately-suppliable, possibly-inconsistent
|
|
298
|
+
number). The All/Success filter is pure CSS (native radio inputs + a general-sibling rule in
|
|
299
|
+
`mcp-ui-render.css`) — no Lit reactive state, same precedent `test-connection-report`'s
|
|
300
|
+
mitigation `<details>` already set. New types: `PermissionGrant`, `PermissionsValidationReport`.
|
|
301
|
+
|
|
302
|
+
### Fixed
|
|
303
|
+
|
|
304
|
+
- **A `test-connection-report`/`permissions-report` field with no value yet still rendered its
|
|
305
|
+
own `<aq-mcp-field>` row** — the control itself correctly rendered nothing, but the field's
|
|
306
|
+
label (e.g. "Validation Report") still showed in the left column next to a blank right column,
|
|
307
|
+
since `<aq-mcp-section>`'s applicability filter only checked `hidden`/`dependencyAttribute`, not
|
|
308
|
+
whether the control had anything to show. Fixed by skipping the field entirely (no
|
|
309
|
+
`<aq-mcp-field>` element at all, not just an empty control body) when one of these two controls
|
|
310
|
+
has a `null`/`undefined` value — `properties-table` is unaffected (it always shows its own
|
|
311
|
+
header/toolbar by design, even with zero rows).
|
|
312
|
+
- **`file` fields always showed `nile-file-upload`'s hardcoded image-upload hint**
|
|
313
|
+
("PNG, JPG or SVG (upto 1MB) | 1:1 ratio") regardless of the field's own `accept`/`maxSizeKB` —
|
|
314
|
+
those props only fed the control's drop/pick VALIDATION, never its displayed `subtitle` text,
|
|
315
|
+
so a non-image field (a PEM certificate, say) still told the user to drop a PNG. Fixed by
|
|
316
|
+
deriving the hint from `field.accept` (extension-like entries only, e.g. `.pem`/`.crt`; bare
|
|
317
|
+
MIME types are skipped) and `field.maxSizeKB`, e.g. `"PEM, CRT or CER (up to 64KB)"`.
|
|
318
|
+
|
|
319
|
+
## [0.4.1] - 2026-08-05
|
|
320
|
+
|
|
321
|
+
### Fixed
|
|
322
|
+
|
|
323
|
+
- **Rendered form controls had no accessible name at all for assistive technology.**
|
|
324
|
+
`<aq-mcp-field>` shows the field's label VISIBLY in its own left-column `<div>` (§12
|
|
325
|
+
two-column layout), but that div was never programmatically associated with the
|
|
326
|
+
control next to it (no `for`/`aria-labelledby`/`aria-label`) — and the `input`/
|
|
327
|
+
`textarea`/`password`/`select` controls never populated Nile's own `label` property
|
|
328
|
+
either, which is what actually drives each control's real accessible-name wiring
|
|
329
|
+
(`for="input"` on `nile-input`/`nile-textarea`, `aria-labelledby="label"` on
|
|
330
|
+
`nile-select`; each is `aria-hidden` when `label` is empty). Net effect: every
|
|
331
|
+
rendered field had zero accessible name for a screen reader. Fixed by passing the
|
|
332
|
+
field's label into Nile's `.label` property on those four controls, then visually
|
|
333
|
+
hiding Nile's own internal label element (`::part(form-control-label)`, the
|
|
334
|
+
standard clip-rect "hidden but announced" pattern — not `display:none`, which would
|
|
335
|
+
also drop it from the accessibility tree) so nothing appears twice on screen.
|
|
336
|
+
Confirmed directly against the live accessibility tree (`aria-hidden="false"`, label
|
|
337
|
+
text matches, zero visual footprint) — not just asserted from source reading.
|
|
338
|
+
- **Known, separate gap — not fixed here:** `checkbox` (`nile-slide-toggle`) has no
|
|
339
|
+
equivalent wiring in Nile itself; its internal `<input type="checkbox">` has no
|
|
340
|
+
`for`/`aria-label`/`aria-labelledby` connecting it to its own `label`/`sublabel`
|
|
341
|
+
properties at all (confirmed by reading its full render template). This is a gap in
|
|
342
|
+
Nile's own component, not something fixable from this library's side — flagged for
|
|
343
|
+
the Nile team, not silently worked around.
|
|
344
|
+
|
|
345
|
+
## [0.4.0] - 2026-08-04
|
|
346
|
+
|
|
347
|
+
### Added
|
|
348
|
+
|
|
349
|
+
- **`sectionsLayout: 'stacked' | 'tabs'`** (`<aq-mcp-config>`, default `'stacked'`) — an
|
|
350
|
+
alternative to the default one-card-per-section layout. `'tabs'` renders one
|
|
351
|
+
[`nile-nav-tab`](https://nile.aqueralabs.com/2.0.1/nav-tab) per section, with the
|
|
352
|
+
section's own card as that tab's panel (rendered `compact`: no border/title, since the
|
|
353
|
+
tab label already shows it). Validation, submit, and footers behave identically in
|
|
354
|
+
either layout — only the arrangement changes.
|
|
355
|
+
- **`tabsPlacement: 'top' | 'bottom' | 'start' | 'end'`** (`<aq-mcp-config>`, default
|
|
356
|
+
`'top'`) — tab orientation, only meaningful in `sectionsLayout: 'tabs'`. `'start'`/`'end'`
|
|
357
|
+
give a fixed-width vertical tab list (label truncates with an ellipsis + native `title`
|
|
358
|
+
tooltip past 170px, using `nile-nav-tab-group`'s own `width` attribute to prevent layout
|
|
359
|
+
shift between tabs, per its documented purpose).
|
|
360
|
+
- **`tabsShowErrorIndicator`** (`<aq-mcp-config>`, default `true`) — a small red dot next to
|
|
361
|
+
a tab's label once that section has an actual recorded validation error (present in the
|
|
362
|
+
form's live `errors` state) — never on a pristine, untouched required field. Rendered via
|
|
363
|
+
`nile-nav-tab`'s documented `suffix` slot (not the default label slot, which gets
|
|
364
|
+
squeezed to zero width in vertical placement). Set `false` to opt out.
|
|
365
|
+
- **`compact`** (`<aq-mcp-section>`, default `false`) — drops a section's own card
|
|
366
|
+
border/title when it's already framed by something else (set automatically by
|
|
367
|
+
`<aq-mcp-config>` in tabs mode; available for a host embedding `<aq-mcp-section>`
|
|
368
|
+
standalone inside its own tabbed/framed UI).
|
|
369
|
+
- New `mcp-render-demo` example: a "Sections: Stacked / Tabs (horizontal) / Tabs
|
|
370
|
+
(vertical)" cycling control wired to `sectionsLayout`/`tabsPlacement`.
|
|
371
|
+
- **`CONFIGURATION.md`** — a dedicated reference documenting every property, method, and
|
|
372
|
+
event on `<aq-mcp-config>` / `<aq-mcp-section>` / `<aq-mcp-field>`, plus the
|
|
373
|
+
`registerControl`/`registerSectionFooter` extensibility registries, linked from
|
|
374
|
+
`README.md`.
|
|
375
|
+
|
|
376
|
+
## [0.3.1] - 2026-08-04
|
|
377
|
+
|
|
378
|
+
### Fixed
|
|
379
|
+
|
|
380
|
+
- **`writeOnce` password fields (e.g. `datatype: "password"` with `writeOnce: true`) discarded
|
|
381
|
+
keystrokes and flipped back to the masked "••••••• Replace" view after the very first
|
|
382
|
+
character typed into a blank field.** Two compounding bugs:
|
|
383
|
+
- The `password` control (`controls.ts`) hardcoded `<nile-input>`'s `.value` to `''` on
|
|
384
|
+
every render instead of binding the actual current `value` — so every re-render
|
|
385
|
+
(triggered by each keystroke) forcibly reset the input back to empty.
|
|
386
|
+
- `AqMcpField.isSetWriteOnce()` treated **any non-empty string** as "a value is already
|
|
387
|
+
stored" (`typeof this.value === 'string' && this.value !== ''`), rather than checking
|
|
388
|
+
specifically for the `WRITE_ONCE_UNCHANGED` sentinel. So the moment a user typed even one
|
|
389
|
+
character into a genuinely blank field, that keystroke itself satisfied "non-empty
|
|
390
|
+
string" and flipped the field to the masked/Replace view mid-type, discarding everything
|
|
391
|
+
typed after the first character.
|
|
392
|
+
- Both fixed: the control now binds `.value` like every other control, and
|
|
393
|
+
`isSetWriteOnce()` now checks `this.value === WRITE_ONCE_UNCHANGED` specifically. The
|
|
394
|
+
genuine "already-stored value → click Replace → type a new one" flow is unaffected and
|
|
395
|
+
still masks correctly.
|
|
396
|
+
|
|
397
|
+
## [0.3.0] - 2026-08-04
|
|
398
|
+
|
|
399
|
+
### Added
|
|
400
|
+
|
|
401
|
+
- **`showFooter` / `showSectionFooters`** boolean properties (`<aq-mcp-section>` /
|
|
402
|
+
`<aq-mcp-config>`, default `true`) — a master switch that hides a section's footer
|
|
403
|
+
entirely, whether it's the library's default "Save `<label>`" button **or** a
|
|
404
|
+
`registerSectionFooter` override. Lets a host that drives submission itself (its own
|
|
405
|
+
external button) turn footers off with one property instead of registering an empty
|
|
406
|
+
footer for every section key.
|
|
407
|
+
- **`AqMcpConfig.validateAll(): { valid: boolean; values: Values }`** — a public method a
|
|
408
|
+
host can call imperatively (e.g. via an Angular template ref) to validate every
|
|
409
|
+
section's applicable fields at once and get the full form state back. Pushes results
|
|
410
|
+
into the element's own `errors` state — the same mechanism a section's own Save button
|
|
411
|
+
uses via `validateSection` — so a host-driven "submit everything" button gets real
|
|
412
|
+
validation and inline field-error display, not just raw `values`.
|
|
413
|
+
- **Live on-change validation** — editing a field now re-validates its actual new value
|
|
414
|
+
immediately (`onFieldInput`), instead of just optimistically clearing its error and
|
|
415
|
+
waiting for the next submit. An error appears the instant a field goes invalid and
|
|
416
|
+
clears the instant it's genuinely fixed.
|
|
417
|
+
|
|
418
|
+
### Changed
|
|
419
|
+
|
|
420
|
+
- Internally refactored the per-field validation loop into a shared `computeErrors()`
|
|
421
|
+
helper, used by both `validateSection` (one section) and `validateAll` (every section),
|
|
422
|
+
so both paths compute errors identically.
|
|
423
|
+
|
|
424
|
+
## [0.2.0] - 2026-07-31
|
|
425
|
+
|
|
426
|
+
Published externally with no corresponding source changes in this repo — identical
|
|
427
|
+
content to 0.1.0. Recorded here only so the version history has no unexplained gap; see
|
|
428
|
+
0.1.0 below for what it actually contains.
|
|
429
|
+
|
|
430
|
+
## [0.1.0] - 2026-07-29
|
|
431
|
+
|
|
432
|
+
### Added
|
|
433
|
+
|
|
434
|
+
- Initial scaffold: framework-agnostic Lit 3 web components (`<aq-mcp-config>`,
|
|
435
|
+
`<aq-mcp-section>`, `<aq-mcp-field>`) rendering a `bootstrap` tenant-configuration
|
|
436
|
+
descriptor via Nile, with the `aq-mcp-change` / `aq-mcp-submit` / `aq-mcp-action` event
|
|
437
|
+
contract and the `registerControl` / `registerSectionFooter` extensibility registry.
|
package/CONFIGURATION.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
Every property, method, event, and extensibility hook the three custom elements
|
|
4
|
+
(`<aq-mcp-config>`, `<aq-mcp-section>`, `<aq-mcp-field>`) expose. See [`USAGE.md`](./USAGE.md)
|
|
5
|
+
for framework integration examples and [`README.md`](./README.md) for the public API export
|
|
6
|
+
table (`index.ts`).
|
|
7
|
+
|
|
8
|
+
All properties are plain Lit reactive properties set via JS (`el.propName = value`) or, in
|
|
9
|
+
Angular/JSX, via property binding (`[propName]="value"`) — not HTML attributes, except where
|
|
10
|
+
noted (`sectionsLayout`, `tabsPlacement`, `showSectionFooters`, `tabsShowErrorIndicator`,
|
|
11
|
+
`showFooter`, `compact` reflect as attributes too since they're primitives).
|
|
12
|
+
|
|
13
|
+
## `<aq-mcp-config>` — the root element
|
|
14
|
+
|
|
15
|
+
This is the element a host actually mounts. It owns the descriptor + values + validation state,
|
|
16
|
+
renders every section, and is the only element that emits the public event contract.
|
|
17
|
+
|
|
18
|
+
### Properties
|
|
19
|
+
|
|
20
|
+
| Property | Type | Default | Description |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `descriptor` | `Descriptor \| null` | `null` | The `bootstrap` tool's response — required to render anything. |
|
|
23
|
+
| `values` | `Values` (`Record<string, unknown>`) | `{}` | Current form state, keyed by `attribute`. One-way in: the host passes it down; edits flow back out via the `aq-mcp-change` event's `detail.values`, not by mutating this property in place. |
|
|
24
|
+
| `showSectionFooters` | `boolean` | `true` | Master switch for every section's footer — the default "Save `<label>`" button, **and** any `registerSectionFooter` override. `false` hides every footer at once, e.g. when the host drives submission itself via its own button(s). |
|
|
25
|
+
| `sectionsLayout` | `'stacked' \| 'tabs' \| 'accordion'` | `'stacked'` | How sections are arranged. `'stacked'` is every section as its own card, one after another (unchanged legacy behavior). `'tabs'` renders one `nile-nav-tab` per section, with the section's own card as that tab's panel. `'accordion'` renders one `nile-accordion` per section — single-open, with a label-above-control body laid out in one or two columns (`accordionColumns`) and each field's `help` shown as hint text beneath it; a card whose section still has a required field empty tints its edge, so a collapsed section that needs work is findable. Validation and submit behave identically in every layout; footers default to hidden in `'accordion'` (see `accordionShowFooters`). An unrecognised value falls back to `'stacked'`. |
|
|
26
|
+
| `tabsPlacement` | `'top' \| 'bottom' \| 'start' \| 'end'` | `'top'` | Tab orientation, only meaningful when `sectionsLayout === 'tabs'`. Passed straight through to `nile-nav-tab-group`'s own `placement`. `'start'` gives a left-hand vertical tab list; `'end'` a right-hand one. |
|
|
27
|
+
| `tabsShowErrorIndicator` | `boolean` | `true` | Tabs mode only. Shows a small red dot next to a tab's label once that section has an **actual recorded** validation error (i.e. present in the internal `errors` state) — not merely a required field the user hasn't reached yet. An error lands there only after a live edit is validated or the section/whole form has been submitted at least once. `false` opts out entirely. |
|
|
28
|
+
| `accordionOpenSection` | `string \| null` | `null` | Accordion mode only. Which section starts expanded. Left `null`, the library picks the first section with a required-but-empty field, else the first visible section — so the form opens on the part that still needs work. A section `key` pins that section; `''` starts every section collapsed. Only re-evaluated when the descriptor (or this property) changes, never on a value edit — otherwise the card being typed in would slam shut. |
|
|
29
|
+
| `accordionShowFooters` | `boolean` | `false` | Accordion mode only. Shows the open section's "Save `<label>`" footer. Defaults to **false**, unlike the other layouts: an accordion is a whole-form overview, and the host normally drives one submit for all of it via `validateAll()`. `showSectionFooters: false` still overrides this to off. |
|
|
30
|
+
| `sectionIcons` | `Record<string, string>` | `{}` | Accordion mode only. Header icon per `section.key` → `nile-glyph` name. Host map first, then a built-in default for `authorization`/`tenantAttributes`, then a generic glyph. Host-side only — the `bootstrap` format carries no icon field. |
|
|
31
|
+
| `accordionColumns` | `1 \| 2` | `2` | Accordion mode only. How many columns the "defaults you can tune" group uses. A **ceiling, not a fixed count**: at `2` the grid still collapses to one column when the container is too narrow to give each cell a usable width, so `1` means "never two", not "always exactly one". |
|
|
32
|
+
| `dynamicOptions` | `Record<string, unknown[]>` | `{}` | Host-fed options for `select`/`radio` fields resolved dynamically (§9.6) — keyed by `field.attribute`, takes priority over that field's own static `options` when both are present. The library asks for a list via `aq-mcp-options-request`; the host calls the named tool and sets the result back in here (a NEW object each time — `{ ...cfg.dynamicOptions, [attr]: list }` — not a mutated one, so the property-changed check fires). See "Common recipes" below for the full round-trip. |
|
|
33
|
+
|
|
34
|
+
### Methods
|
|
35
|
+
|
|
36
|
+
| Method | Signature | Description |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `validateAll()` | `(): { valid: boolean; values: Values }` | Validates every section's applicable fields at once and pushes the results into the element's own internal `errors` state — the same mechanism a section's own Save button uses (`validateSection`), just across every section. Call it imperatively on the element (e.g. an Angular template ref: `<aq-mcp-config #cfg>` then `cfg.validateAll()`) when the host drives its own "submit everything" button instead of using per-section footers — this is what makes that external button still get real validation + inline field-error display, not just the raw `values`. There is no event for this: the host decides what "submit everything" even means for its own data shape, so it must call the method directly. |
|
|
39
|
+
|
|
40
|
+
### Events
|
|
41
|
+
|
|
42
|
+
All three bubble and are composed (`bubbles: true, composed: true`), so a host listening on any
|
|
43
|
+
ancestor element (or via `addEventListener` on `document`) still receives them.
|
|
44
|
+
|
|
45
|
+
| Event | `detail` shape | Fires when |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `aq-mcp-change` | `{ attribute: string; value: unknown; values: Values }` | Any field is edited. `values` is the **entire current form state** across every section (not scoped to the edited field's section) — this is the only way the host's own copy of `values` stays in sync, since `[values]` is a one-way-in property; write `detail.values` back into whatever the host passed in as `values` if it needs to read the form state later (e.g. from a host-driven submit button). |
|
|
48
|
+
| `aq-mcp-submit` | `{ sectionKey: string; submitTool: string \| null; values: Values }` (`SubmitPayload`) | A section's Save action succeeds validation. `values` here is scoped to **that one section only**, and pre-processed: hidden/`datatype:"none"` fields dropped, inapplicable fields (failed a `dependencyAttribute`/`dependsOn` check, §9.7) dropped, numbers coerced, and a blank/unchanged `writeOnce`/secret field omitted entirely (so "keep current value" doesn't overwrite anything). `submitTool` is the connector tool to call, or `null` meaning the platform default (`applications_update`). Never fires if validation fails — nothing is emitted, the section's fields just show inline errors. |
|
|
49
|
+
| `aq-mcp-action` | `{ tool: string; args: Record<string, unknown>; confirm?: string; field: Field; action?: FieldAction }` | A `datatype: "none"` button field is invoked, OR one entry in a control's own `field.actions` toolbar is clicked (§5, e.g. `properties-table`'s "Download"/"Edit Configuration") — after the user confirms, if a `confirm` message is declared (on the action itself, or on the field, checked in that order). `tool`/`args`/`confirm` resolve from the clicked `action` when present, else from the field's own top-level `tool`/`args`/`confirm`. `action` is the specific `FieldAction` clicked, undefined for the classic single-`button` case. No `values` on this event at all. |
|
|
50
|
+
| `aq-mcp-options-request` | `{ attribute: string; tool: string; args: Record<string, unknown> }` | A `select`/`radio` field declares `tool` and currently has no options (§9.6) — neither a static `field.options` nor an existing `dynamicOptions` entry — AND currently applies (passes its `dependencyAttribute`/`dependsOn` check, if any, §9.7; a field gated behind another field's value is never requested before that value makes it applicable). Fires **at most once per `attribute`** for this element's lifetime; feed the result back via the `dynamicOptions` property. |
|
|
51
|
+
|
|
52
|
+
The library performs no network I/O and holds no secrets: it renders, validates, and emits
|
|
53
|
+
intent; the host is responsible for actually calling the connector/platform tool.
|
|
54
|
+
|
|
55
|
+
## `<aq-mcp-section>` — one section's card
|
|
56
|
+
|
|
57
|
+
Normally only used internally by `<aq-mcp-config>` (one instance per section), but it's a
|
|
58
|
+
public, independently-usable custom element if a host ever wants to render a single section on
|
|
59
|
+
its own outside the full form.
|
|
60
|
+
|
|
61
|
+
### Properties
|
|
62
|
+
|
|
63
|
+
| Property | Type | Default | Description |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `section` | `Section` | *(required)* | The section to render — one entry from `descriptor.sections`. |
|
|
66
|
+
| `values` | `Values` | `{}` | Current form state (the whole form's, not just this section's — fields reference other sections' values via `dependencyAttribute`). |
|
|
67
|
+
| `errors` | `Record<string, string \| null>` | `{}` | Per-field validation messages, keyed by `attribute`. |
|
|
68
|
+
| `showFooter` | `boolean` | `true` | Master switch for **this** section's footer — the default "Save `<label>`" button, AND any `registerSectionFooter` override for this section's key. `false` hides the footer entirely regardless of what's registered. |
|
|
69
|
+
| `compact` | `boolean` | `false` | Set automatically by `<aq-mcp-config>` when this section is rendered as a tab panel (`sectionsLayout: 'tabs'`) — drops the card's own border and title line (the tab nav already shows the label), but keeps the `authType` badge and save-target caption. Set it yourself only if embedding `<aq-mcp-section>` standalone inside some other tabbed/framed UI. |
|
|
70
|
+
| `dynamicOptions` | `Record<string, unknown[]>` | `{}` | Passed straight through to every `<aq-mcp-field>` this section renders — see `<aq-mcp-config>`'s own `dynamicOptions` for the full contract. **Note:** `aq-mcp-options-request` is only ever emitted by `<aq-mcp-config>` (it owns the "which field needs options" scan); `<aq-mcp-section>` used standalone never fires it, so a host embedding it without `<aq-mcp-config>` must resolve any `tool`-backed field's options itself before setting this property — same "more on the host" trade-off already true for standalone validation/submit below. |
|
|
71
|
+
|
|
72
|
+
Emits the internal `section-submit` event (`{ detail: { sectionKey } }`) when its footer's Save
|
|
73
|
+
button is clicked — this is consumed by `<aq-mcp-config>`, not part of the public host contract;
|
|
74
|
+
a host embedding `<aq-mcp-section>` standalone needs to listen for it directly and call its own
|
|
75
|
+
validation/submit logic (there's no `<aq-mcp-config>` there to do it automatically).
|
|
76
|
+
|
|
77
|
+
## `<aq-mcp-field>` — one field's row
|
|
78
|
+
|
|
79
|
+
Independent of `<aq-mcp-config>`/`<aq-mcp-section>` entirely — the simplest way to render a
|
|
80
|
+
single control (e.g. dropping a `test-connection-report` result into an arbitrary div; see
|
|
81
|
+
`README.md`'s "Rendering a single `ui-component`" section for a worked example).
|
|
82
|
+
|
|
83
|
+
### Properties
|
|
84
|
+
|
|
85
|
+
| Property | Type | Default | Description |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `field` | `Field` | *(required)* | The field definition to render (`attribute`, `datatype`, `ui-component`, `isRequired`, `validation`, etc. — see the descriptor contract). |
|
|
88
|
+
| `value` | `unknown` | `undefined` | The field's current value. |
|
|
89
|
+
| `error` | `string \| null` | `null` | Validation message to show, or `null` when valid. |
|
|
90
|
+
| `dynamicOptions` | `Record<string, unknown[]>` | `{}` | Forwarded to the `select`/`radio` control renderer as `ControlContext.dynamicOptions` (§9.6) — see `<aq-mcp-config>`'s own `dynamicOptions` for the full contract. `<aq-mcp-field>` never emits `aq-mcp-options-request` itself (that scan lives in `<aq-mcp-config>`); a standalone `<aq-mcp-field>` for a `tool`-backed field needs this set directly by the host up front. |
|
|
91
|
+
|
|
92
|
+
A `writeOnce` field (e.g. a password) whose `value` is exactly the `WRITE_ONCE_UNCHANGED`
|
|
93
|
+
sentinel (exported from `index.ts`) renders masked + a "Replace" link instead of an input, until
|
|
94
|
+
Replace is clicked — this is a stored-value indicator, not a truthy-string check, so typing into
|
|
95
|
+
an otherwise-empty `writeOnce` field never gets misread as "already set."
|
|
96
|
+
|
|
97
|
+
A `file` field's `accept`/`maxSizeKB` validation is **client-side only, advisory** — this library
|
|
98
|
+
has no network I/O (D-4), so it can't be the real gate. A connector must still enforce file
|
|
99
|
+
type/size server-side; treat the in-browser check as a fast first pass for the user, not a
|
|
100
|
+
security boundary.
|
|
101
|
+
|
|
102
|
+
Emits the internal `field-input` (`{ detail: { attribute, value } }`) and `field-action`
|
|
103
|
+
(`{ detail: { field } }`) events — again consumed by `<aq-mcp-config>`/`<aq-mcp-section>`, not
|
|
104
|
+
part of the public contract for a standalone embed.
|
|
105
|
+
|
|
106
|
+
## Extensibility registries (`index.ts` exports)
|
|
107
|
+
|
|
108
|
+
Two global, side-effecting registries — call these once at app bootstrap, before the relevant
|
|
109
|
+
`<aq-mcp-*>` element first renders.
|
|
110
|
+
|
|
111
|
+
| Function | Signature | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `registerControl` | `(name: string, renderer: ControlRenderer) => void` | Register or override the Lit renderer for a `ui-component` string. An unknown `ui-component` always falls back to the field's datatype default (`registry.ts`'s `defaultControlFor`) — a field is never silently dropped. |
|
|
114
|
+
| `resolveControl` | `(uiComponent: string) => ControlRenderer \| undefined` | Look up a registered control renderer directly (used internally by `<aq-mcp-field>`; also usable by a host bypassing `<aq-mcp-field>` entirely — see README). |
|
|
115
|
+
| `registerSectionFooter` | `(key: string, renderer: SectionFooterRenderer) => void` | Register or override the footer for a section, keyed by `section.key`. Replaces the library's default "Save `<label>`" button entirely for that key — the renderer owns the markup and must call `ctx.onSubmit()` itself to actually trigger validation + `aq-mcp-submit`. This override always takes precedence over `showSectionFooters`/`showFooter` being `true`, but **not** over them being `false` — the boolean is a master kill-switch checked first (see `AqMcpSection.renderFooter`). |
|
|
116
|
+
| `resolveSectionFooter` | `(key: string) => SectionFooterRenderer \| undefined` | Look up a registered footer renderer directly. |
|
|
117
|
+
|
|
118
|
+
`ControlContext` (what a control renderer receives): `{ field, value, error, onInput, onAction?, dynamicOptions? }`.
|
|
119
|
+
`SectionFooterContext` (what a footer renderer receives): `{ section, values, onSubmit }`.
|
|
120
|
+
|
|
121
|
+
## Common recipes
|
|
122
|
+
|
|
123
|
+
**Hide every section's default footer, drive submission from the host's own button:**
|
|
124
|
+
```ts
|
|
125
|
+
cfg.showSectionFooters = false;
|
|
126
|
+
// ...
|
|
127
|
+
const { valid, values } = cfg.validateAll(); // real validation, not just raw values
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Tabs, vertical, with the error dot:**
|
|
131
|
+
```html
|
|
132
|
+
<aq-mcp-config
|
|
133
|
+
[descriptor]="descriptor" [values]="values"
|
|
134
|
+
sectionsLayout="tabs" tabsPlacement="start"
|
|
135
|
+
></aq-mcp-config>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Feed a `select`/`radio` field's options from a tool call (§9.6):**
|
|
139
|
+
```ts
|
|
140
|
+
cfg.addEventListener('aq-mcp-options-request', async (e) => {
|
|
141
|
+
const { attribute, tool, args } = e.detail; // e.g. tool: 'listFtpServers'
|
|
142
|
+
const options = await host.callTool(tool, args); // string[] or {value,label}[]
|
|
143
|
+
cfg.dynamicOptions = { ...cfg.dynamicOptions, [attribute]: options };
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Replace one section's footer, keep the rest default:**
|
|
148
|
+
```ts
|
|
149
|
+
import { registerSectionFooter } from '@aquera/mcp-ui-render';
|
|
150
|
+
import { html } from 'lit';
|
|
151
|
+
|
|
152
|
+
registerSectionFooter('authorization', ({ onSubmit }) => html`
|
|
153
|
+
<div class="aq-mcp-section__footer">
|
|
154
|
+
<nile-button variant="primary" @click=${onSubmit}>Save & encrypt credentials</nile-button>
|
|
155
|
+
</div>
|
|
156
|
+
`);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Customize `properties-table`'s action toolbar (`Field.actions`) beyond the built-in
|
|
160
|
+
icon/labeled-button default** — there's no separate action-renderer hook; `registerControl`
|
|
161
|
+
overrides the whole control, toolbar included, same as customizing any other built-in:
|
|
162
|
+
```ts
|
|
163
|
+
import { registerControl } from '@aquera/mcp-ui-render';
|
|
164
|
+
import { html } from 'lit';
|
|
165
|
+
|
|
166
|
+
registerControl('properties-table', ({ field, value, onAction }) => {
|
|
167
|
+
const rows = value ?? field.rows ?? [];
|
|
168
|
+
return html`
|
|
169
|
+
<div class="my-config-card">
|
|
170
|
+
<h3>${field.label}</h3>
|
|
171
|
+
<nile-dropdown>
|
|
172
|
+
${(field.actions ?? []).map((a) => html`<nile-menu-item @click=${() => onAction?.(a)}>${a.label}</nile-menu-item>`)}
|
|
173
|
+
</nile-dropdown>
|
|
174
|
+
<!-- ... your own row rendering ... -->
|
|
175
|
+
</div>
|
|
176
|
+
`;
|
|
177
|
+
});
|
|
178
|
+
```
|