@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,811 @@
1
+ /* Default "sectioned form" layout for @aquera/mcp-ui-render (design Option A):
2
+ * a card per section, two-column rows (label+help left, control right), an
3
+ * authType badge + save-target caption in the header, and a save footer per
4
+ * card. Styled with Nile nxtgen tokens so it inherits the host theme. Hosts may
5
+ * override any of these classes. Import once: import '@aquera/mcp-ui-render/styles.css'. */
6
+
7
+ .aq-mcp {
8
+ display: block;
9
+ max-width: 920px;
10
+ }
11
+
12
+ .aq-mcp-section {
13
+ background: var(--ng-colors-bg-primary, #fff);
14
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
15
+ border-radius: 12px;
16
+ margin-bottom: 20px;
17
+ overflow: hidden;
18
+ }
19
+
20
+ .aq-mcp-section__header {
21
+ padding: 20px 24px 14px;
22
+ }
23
+ .aq-mcp-section__title {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 10px;
27
+ font-size: 16px;
28
+ font-weight: 700;
29
+ color: var(--ng-colors-text-primary-900, #101828);
30
+ }
31
+ .aq-mcp-section__caption {
32
+ margin-top: 6px;
33
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
34
+ font-size: 12px;
35
+ color: var(--ng-colors-text-tertiary-600, #667085);
36
+ }
37
+
38
+ .aq-mcp-section__body {
39
+ padding: 0 24px;
40
+ }
41
+
42
+ .aq-mcp-field {
43
+ display: grid;
44
+ grid-template-columns: 280px 1fr;
45
+ gap: 24px;
46
+ align-items: start;
47
+ padding: 18px 0;
48
+ border-top: 1px solid var(--ng-colors-border-secondary-alt, #f0f1f4);
49
+ }
50
+ .aq-mcp-field:first-child {
51
+ border-top: 0;
52
+ }
53
+ .aq-mcp-field__label {
54
+ font-size: 14px;
55
+ font-weight: 600;
56
+ color: var(--ng-colors-text-secondary-700, #344054);
57
+ }
58
+ .aq-mcp-field__req {
59
+ color: var(--ng-color-error-500, #d92d20);
60
+ margin-left: 2px;
61
+ }
62
+ .aq-mcp-field__help {
63
+ margin-top: 4px;
64
+ font-size: 13px;
65
+ font-weight: 400;
66
+ line-height: 1.45;
67
+ color: var(--ng-colors-text-tertiary-600, #667085);
68
+ }
69
+ .aq-mcp-field__help-toggle {
70
+ display: inline;
71
+ margin-left: 4px;
72
+ padding: 0;
73
+ border: 0;
74
+ background: none;
75
+ font-size: 13px;
76
+ font-weight: 600;
77
+ color: var(--ng-colors-brand-600, #7f56d9);
78
+ cursor: pointer;
79
+ }
80
+ .aq-mcp-field__control {
81
+ min-width: 0;
82
+ }
83
+
84
+ /* `input`/`textarea`/`password`/`select`/`radio` are given Nile's own `label`
85
+ * (see controls.ts) purely so each control wires its real, native accessible-name
86
+ * association (`for`/`aria-labelledby`) — Nile leaves that association
87
+ * `aria-hidden` when `label` is empty. This library already shows the label
88
+ * VISIBLY in `.aq-mcp-field__label` (the left column, §12 two-column
89
+ * layout), so Nile's own internal label element is visually hidden here —
90
+ * NOT `display:none`/`visibility:hidden`, which would also drop it from the
91
+ * accessibility tree and defeat the whole point; this is the standard
92
+ * clip-rect "visually hidden, still announced" pattern. */
93
+ .aq-mcp-field__control nile-input::part(form-control-label),
94
+ .aq-mcp-field__control nile-textarea::part(form-control-label),
95
+ .aq-mcp-field__control nile-select::part(form-control-label),
96
+ .aq-mcp-field__control nile-radio-group::part(form-control-label) {
97
+ position: absolute;
98
+ width: 1px;
99
+ height: 1px;
100
+ padding: 0;
101
+ margin: -1px;
102
+ overflow: hidden;
103
+ clip: rect(0, 0, 0, 0);
104
+ white-space: nowrap;
105
+ border: 0;
106
+ }
107
+
108
+ /* write-once secret in "set" state — shown as a STATE, not a masked value
109
+ (§7): the host holds the real secret and this element never sees it, so a
110
+ row of dots would imply there is something here to reveal. */
111
+ .aq-mcp-writeonce {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 12px;
115
+ font-size: 14px;
116
+ color: var(--ng-colors-text-secondary-700, #344054);
117
+ }
118
+ .aq-mcp-field--dense .aq-mcp-writeonce {
119
+ padding: 10px 12px;
120
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
121
+ border-radius: 8px;
122
+ background: var(--ng-colors-bg-primary, #fff);
123
+ }
124
+ .aq-mcp-writeonce__lock {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ flex: 0 0 auto;
129
+ width: 26px;
130
+ height: 26px;
131
+ border-radius: 7px;
132
+ background: var(--ng-color-success-50, #ecfdf3);
133
+ color: var(--ng-color-success-600, #079455);
134
+ }
135
+ .aq-mcp-writeonce__state {
136
+ flex: 1 1 auto;
137
+ font-size: 13.5px;
138
+ }
139
+ .aq-mcp-writeonce__replace {
140
+ flex: 0 0 auto;
141
+ padding: 6px 12px;
142
+ border: 1px solid var(--ng-colors-border-primary, #d0d5dd);
143
+ border-radius: 8px;
144
+ background: var(--ng-colors-bg-primary, #fff);
145
+ color: var(--ng-colors-text-secondary-700, #344054);
146
+ font: inherit;
147
+ font-size: 13px;
148
+ font-weight: 600;
149
+ cursor: pointer;
150
+ }
151
+ .aq-mcp-writeonce__replace:hover {
152
+ background: var(--ng-colors-bg-secondary, #f9fafb);
153
+ }
154
+
155
+ .aq-mcp-section__footer {
156
+ display: flex;
157
+ justify-content: flex-end;
158
+ padding: 16px 24px;
159
+ border-top: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
160
+ background: var(--ng-colors-bg-secondary, #f9fafb);
161
+ }
162
+
163
+ /* `sectionsLayout: 'tabs'` (aq-mcp-config) — each section's own card loses its
164
+ * border/background/margin when nested in a tab panel (the tab strip already
165
+ * frames it), and its header drops the title line (the tab nav already shows
166
+ * the label) but keeps the authType badge + save-target caption inline. */
167
+ .aq-mcp-tabs {
168
+ display: block;
169
+ margin-bottom: 24px;
170
+ background: var(--ng-colors-bg-primary, #fff);
171
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
172
+ border-radius: 12px;
173
+ overflow: hidden;
174
+ }
175
+ .aq-mcp-tabs::part(nav) {
176
+ /* Vertical padding, not just horizontal — with only `0 24px`, tab labels
177
+ * sat flush against the card's top edge with no room to breathe. */
178
+ padding: 14px 24px 0;
179
+ background: var(--ng-colors-bg-secondary, #f9fafb);
180
+ }
181
+ /* `body` is nile-nav-tab-group's own documented part name for "the container
182
+ * where the panel content stays" (there's no per-panel `base` part). */
183
+ .aq-mcp-tabs::part(body) {
184
+ padding: 20px 24px 24px;
185
+ }
186
+ /* Vertical placement ('start'/'end'): the nav column sits directly beside the
187
+ * content with no card padding of its own — give it room to breathe. Column
188
+ * width itself comes from nile-nav-tab-group's own `width` attribute (set in
189
+ * aq-mcp-config.ts), not CSS — that's its documented purpose (fixed per-tab
190
+ * width, preventing layout shift when switching tabs). */
191
+ .aq-mcp-tabs[placement='start']::part(nav),
192
+ .aq-mcp-tabs[placement='end']::part(nav) {
193
+ padding: 16px 12px;
194
+ background: var(--ng-colors-bg-secondary, #f9fafb);
195
+ }
196
+ .aq-mcp-tabs[placement='start']::part(body),
197
+ .aq-mcp-tabs[placement='end']::part(body) {
198
+ padding: 24px;
199
+ }
200
+
201
+ /* A section's tab label: left-aligned, never wraps. In horizontal placement
202
+ * tabs size to their own content as normal (unconstrained) — only vertical
203
+ * placement gets a hard max-width + ellipsis, since that's the mode with a
204
+ * fixed-width nav column (`width` attribute, aq-mcp-config.ts) a long label
205
+ * can actually overflow. nile-nav-tab's slotted content isn't clipped by an
206
+ * ancestor flex container by default, so a plain `flex:1` on this element
207
+ * does nothing — `max-width` + `overflow:hidden` is the self-contained
208
+ * truncation that doesn't depend on the parent's layout. `title` (set in
209
+ * aq-mcp-config.ts) gives the full label on hover once truncated. */
210
+ .aq-mcp-tab-label {
211
+ display: block;
212
+ text-align: left;
213
+ }
214
+ .aq-mcp-tabs[placement='start'] .aq-mcp-tab-label,
215
+ .aq-mcp-tabs[placement='end'] .aq-mcp-tab-label {
216
+ max-width: 170px;
217
+ overflow: hidden;
218
+ text-overflow: ellipsis;
219
+ white-space: nowrap;
220
+ }
221
+
222
+ /* Tab error-indicator (tabsShowErrorIndicator, default true) — a small red
223
+ * dot after the tab's label, shown only once that section has an ACTUAL
224
+ * recorded validation error (this.errors), not merely an untouched required
225
+ * field — see sectionHasError()'s doc comment. Slotted as `slot="suffix"`
226
+ * (nile-nav-tab's own documented slot for content after the label), not the
227
+ * default slot — the default slot's label track gets truncated/sized
228
+ * independently in vertical placement, which squeezed a default-slotted dot
229
+ * to 0 width; `suffix` doesn't share that sizing. */
230
+ .aq-mcp-tab-error-dot {
231
+ display: inline-block;
232
+ width: 6px;
233
+ height: 6px;
234
+ flex: 0 0 auto;
235
+ margin-left: 8px;
236
+ border-radius: 50%;
237
+ background: var(--ng-color-error-500, #d92d20);
238
+ vertical-align: middle;
239
+ }
240
+
241
+ .aq-mcp-section--compact {
242
+ background: transparent;
243
+ border: none;
244
+ border-radius: 0;
245
+ margin-bottom: 0;
246
+ }
247
+ .aq-mcp-section__header--compact {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 12px;
251
+ padding: 0 0 16px;
252
+ border-bottom: 1px solid var(--ng-colors-border-secondary-alt, #f0f1f4);
253
+ margin-bottom: 4px;
254
+ }
255
+ .aq-mcp-section__header--compact .aq-mcp-section__caption {
256
+ margin-top: 0;
257
+ }
258
+ .aq-mcp-section--compact .aq-mcp-section__body {
259
+ padding: 0;
260
+ }
261
+ .aq-mcp-section--compact .aq-mcp-section__footer {
262
+ padding: 16px 0 0;
263
+ margin-top: 4px;
264
+ background: transparent;
265
+ }
266
+
267
+ /* ── `sectionsLayout: 'accordion'` ─────────────────────────────────────────
268
+ * One `nile-accordion` per section, single-open, each header summarising what
269
+ * is inside (field count + completion chip) so a COLLAPSED section still says
270
+ * whether it needs attention. The open card's body is a two-column grid of
271
+ * label-above-control cells — a denser shape than the label-left rows the
272
+ * other two layouts use, which is what makes a whole multi-section form
273
+ * scannable at once.
274
+ *
275
+ * Everything here is `::part()` on Nile's own components, never a descendant
276
+ * selector into their shadow DOM — same approach as `.aq-mcp-tabs::part(nav)`
277
+ * above. Token-with-hex-fallback matches the rest of this file. */
278
+
279
+ /* The dense grid wants the full container; `.aq-mcp`'s 920px cap is tuned for
280
+ the single-column label-left rows and would squeeze two columns into
281
+ unusable widths. Scoped to this layout so stacked/tabs are untouched. */
282
+ .aq-mcp--accordion {
283
+ max-width: 1180px;
284
+ }
285
+
286
+ .aq-mcp-acc-list {
287
+ display: block;
288
+ margin-bottom: 24px;
289
+ }
290
+
291
+ /* Each accordion is its own card. Nile's default is a border-TOP only (a
292
+ stacked list of rows); the design is separated cards, so the base part gets
293
+ a full border, a radius, and its own vertical rhythm. */
294
+ .aq-mcp-acc {
295
+ display: block;
296
+ margin-bottom: 12px;
297
+ }
298
+ .aq-mcp-acc::part(base) {
299
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
300
+ border-radius: 12px;
301
+ background: var(--ng-colors-bg-primary, #fff);
302
+ overflow: hidden;
303
+ }
304
+ /* No per-card edge tint: the header's own "N required left" line is the
305
+ signal now, and a coloured border around the whole card on top of it
306
+ double-reports the same fact while making a merely-incomplete section look
307
+ like an error. */
308
+
309
+ .aq-mcp-acc::part(header) {
310
+ padding: 16px 20px;
311
+ }
312
+ .aq-mcp-acc::part(summary-icon) {
313
+ color: var(--ng-colors-text-tertiary-600, #667085);
314
+ }
315
+ /* Nile indents arrow-left content to clear the chevron; the grid below wants
316
+ the card's full width, and the header's own padding already aligns it. */
317
+ .aq-mcp-acc::part(content) {
318
+ margin-left: 0;
319
+ padding: 0 20px 20px;
320
+ }
321
+
322
+ .aq-mcp-acc__summary {
323
+ display: flex;
324
+ align-items: center;
325
+ gap: 14px;
326
+ width: 100%;
327
+ min-width: 0;
328
+ }
329
+ /* The icon tile: a soft brand-tinted square, not a bare glyph. It gives the
330
+ header a fixed left anchor so titles of different lengths still line up
331
+ down the stack of cards. */
332
+ .aq-mcp-acc__icon {
333
+ display: inline-flex;
334
+ align-items: center;
335
+ justify-content: center;
336
+ flex: 0 0 auto;
337
+ width: 38px;
338
+ height: 38px;
339
+ border-radius: 10px;
340
+ background: var(--ng-colors-bg-brand-primary, #eff4ff);
341
+ color: var(--ng-colors-fg-brand-primary-600, #155eef);
342
+ }
343
+ .aq-mcp-acc__heading {
344
+ display: flex;
345
+ align-items: center;
346
+ gap: 10px;
347
+ min-width: 0;
348
+ flex: 1 1 auto;
349
+ }
350
+ .aq-mcp-acc__label {
351
+ font-size: 15px;
352
+ font-weight: 700;
353
+ color: var(--ng-colors-text-primary-900, #101828);
354
+ overflow: hidden;
355
+ text-overflow: ellipsis;
356
+ white-space: nowrap;
357
+ }
358
+
359
+ /* Status sits beside the chevron as plain text, not a filled pill — a pill
360
+ here competes with the section title for the eye, and this is a readout,
361
+ not a control. Colour is never the only signal: each state carries both a
362
+ mark (check or dot) and its own words. */
363
+ .aq-mcp-acc__state {
364
+ display: inline-flex;
365
+ align-items: center;
366
+ gap: 6px;
367
+ flex: 0 0 auto;
368
+ font-size: 13px;
369
+ font-weight: 600;
370
+ white-space: nowrap;
371
+ }
372
+ .aq-mcp-acc__state--complete {
373
+ color: var(--ng-color-success-600, #079455);
374
+ }
375
+ .aq-mcp-acc__state--incomplete {
376
+ color: var(--ng-color-warning-600, #dc6803);
377
+ }
378
+ .aq-mcp-acc__dot {
379
+ display: inline-block;
380
+ width: 7px;
381
+ height: 7px;
382
+ border-radius: 50%;
383
+ background: currentColor;
384
+ }
385
+
386
+ /* ── dense section body: three zones ── */
387
+ /* The open card splits into "things you must decide" (full width), "defaults
388
+ you can tune" (two-up under a rule), and boolean toggles (full-width rows).
389
+ See `zones()` in aq-mcp-section.ts for why this reorders the descriptor. */
390
+ .aq-mcp-section--dense {
391
+ background: transparent;
392
+ border: none;
393
+ border-radius: 0;
394
+ margin-bottom: 0;
395
+ overflow: visible;
396
+ }
397
+ .aq-mcp-section__body--dense {
398
+ display: block;
399
+ padding: 0;
400
+ }
401
+ .aq-mcp-zone {
402
+ display: grid;
403
+ grid-template-columns: minmax(0, 1fr);
404
+ gap: 18px;
405
+ align-items: start;
406
+ }
407
+ .aq-mcp-zone--toggles {
408
+ gap: 10px;
409
+ margin-top: 18px;
410
+ }
411
+ .aq-mcp-zone--defaults {
412
+ margin-top: 4px;
413
+ }
414
+ /* Two columns when there is room, one when there is not — WITHOUT a media
415
+ * query, deliberately. What matters is the width of THIS GRID, not of the
416
+ * window: the same form is ~1180px wide on its own page and ~400px next to a
417
+ * docked side panel at an identical viewport, and a media query cannot tell
418
+ * those two apart. `auto-fit` reacts to the real container instead.
419
+ *
420
+ * Why the track list is shaped this way, and why the 13 is not a typo for 12:
421
+ *
422
+ * gap g = 24px
423
+ * min track m = max(240px, C/2 - 13px) for a container of width C
424
+ * columns n = floor((C + g) / (m + g))
425
+ *
426
+ * The `C/2` term is what CAPS the count at two: three tracks would need
427
+ * 3m + 2g = 1.5C - 39 + 48, which exceeds C for every C, so a very wide
428
+ * container gets WIDER columns rather than a third one. The `240px` floor is
429
+ * what drops it to one column when a half-width cell would be too cramped for
430
+ * a label, control and hint line.
431
+ *
432
+ * The subtraction must be slightly MORE than half the gap. At the exact value
433
+ * (12px = g/2), two tracks plus the gap come to precisely 100% of the
434
+ * container — measured live at 834px available against 834px needed — and
435
+ * that dead heat is decided by subpixel rounding, which loses: the grid
436
+ * silently collapsed to one column at widths that clearly had room for two.
437
+ * The extra pixel buys slack so the arithmetic resolves the same way at every
438
+ * fractional container width, and costs only 1px of minimum track width. */
439
+ .aq-mcp-zone--two-up {
440
+ grid-template-columns: repeat(auto-fit, minmax(max(240px, calc(50% - 13px)), 1fr));
441
+ column-gap: 24px;
442
+ }
443
+ /* A plain hairline between the fields the user must set and the ones the
444
+ connector already defaulted. Deliberately UNCAPTIONED: a heading here would
445
+ be the renderer naming a group the descriptor never described. */
446
+ .aq-mcp-zone__rule {
447
+ height: 1px;
448
+ margin: 26px 0 20px;
449
+ background: var(--ng-colors-border-secondary-alt, #f0f1f4);
450
+ }
451
+
452
+ .aq-mcp-section--dense .aq-mcp-section__footer {
453
+ align-items: center;
454
+ justify-content: space-between;
455
+ padding: 16px 0 0;
456
+ margin-top: 18px;
457
+ background: transparent;
458
+ border-top: 1px solid var(--ng-colors-border-secondary-alt, #f0f1f4);
459
+ }
460
+
461
+ /* ── dense field cell ── */
462
+ /* Overrides `.aq-mcp-field`'s two-column row: one column, label above the
463
+ control, no row separator (the zone gap does that job). */
464
+ .aq-mcp-field--dense {
465
+ display: block;
466
+ padding: 0;
467
+ border-top: 0;
468
+ min-width: 0;
469
+ }
470
+ /* `datatype: "none"` (buttons, reports, tables) renders control-only and
471
+ spans the grid — see `renderDense`. */
472
+ .aq-mcp-field--dense:has(> .aq-mcp-field__control--full) {
473
+ grid-column: 1 / -1;
474
+ }
475
+ .aq-mcp-field--dense .aq-mcp-field__label {
476
+ display: flex;
477
+ align-items: center;
478
+ gap: 8px;
479
+ margin-bottom: 6px;
480
+ font-size: 13px;
481
+ font-weight: 600;
482
+ color: var(--ng-colors-text-secondary-700, #344054);
483
+ }
484
+ .aq-mcp-field__label-text {
485
+ overflow: hidden;
486
+ text-overflow: ellipsis;
487
+ white-space: nowrap;
488
+ }
489
+ /* "Needs a value" — shown only while the requirement is outstanding (see
490
+ renderRequiredMark), so it reads as a to-do rather than as decoration.
491
+ Amber, not red: nothing is wrong yet, it simply is not done. */
492
+ .aq-mcp-field--dense .aq-mcp-field__req {
493
+ flex: 0 0 auto;
494
+ margin-left: 0;
495
+ padding: 2px 8px;
496
+ border: 1px solid var(--ng-color-warning-300, #fec84b);
497
+ border-radius: var(--ng-r-full, 999px);
498
+ background: var(--ng-color-warning-50, #fffaeb);
499
+ color: var(--ng-color-warning-700, #b54708);
500
+ font-size: 11px;
501
+ font-weight: 600;
502
+ letter-spacing: 0.01em;
503
+ }
504
+ /* The pass mark on a field that satisfies its own `validation` rules. */
505
+ .aq-mcp-field__valid {
506
+ color: var(--ng-color-success-600, #079455);
507
+ }
508
+ /* Help sits UNDER the control as hint text (not in a tooltip): always
509
+ visible, no hover or tap needed, and it reflows with the cell instead of
510
+ floating over the card. Same `.aq-mcp-field__help` block the other layouts
511
+ render, so its "View more/less" truncation behaves identically here. */
512
+ .aq-mcp-field--dense .aq-mcp-field__help {
513
+ margin-top: 6px;
514
+ }
515
+
516
+ /* ── dense toggle row ── */
517
+ /* A boolean is its own labelled block rather than a label-above-control cell:
518
+ the control is small enough that stacking a label over it wastes the row,
519
+ and the description belongs inside the block, not hanging beneath it. */
520
+ .aq-mcp-field--dense[data-toggle] {
521
+ grid-column: 1 / -1;
522
+ }
523
+ .aq-mcp-field--dense[data-toggle] .aq-mcp-field__toggle {
524
+ display: flex;
525
+ align-items: flex-start;
526
+ gap: 16px;
527
+ padding: 14px 16px;
528
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
529
+ border-radius: 10px;
530
+ background: var(--ng-colors-bg-primary, #fff);
531
+ }
532
+ .aq-mcp-field__toggle-text {
533
+ display: flex;
534
+ flex-direction: column;
535
+ gap: 3px;
536
+ flex: 1 1 auto;
537
+ min-width: 0;
538
+ }
539
+ .aq-mcp-field--dense[data-toggle] .aq-mcp-field__label-text {
540
+ font-size: 13px;
541
+ font-weight: 600;
542
+ color: var(--ng-colors-text-secondary-700, #344054);
543
+ white-space: normal;
544
+ }
545
+ .aq-mcp-field__toggle-help {
546
+ font-size: 12.5px;
547
+ line-height: 1.5;
548
+ color: var(--ng-colors-text-tertiary-600, #667085);
549
+ }
550
+ .aq-mcp-field--dense[data-toggle] .aq-mcp-field__control {
551
+ flex: 0 0 auto;
552
+ padding-top: 2px;
553
+ }
554
+ .aq-mcp-field--dense[data-toggle][data-on] .aq-mcp-field__toggle {
555
+ border-color: var(--ng-colors-border-brand, #b2ccff);
556
+ background: var(--ng-colors-bg-brand-primary, #eff4ff);
557
+ }
558
+
559
+ /* `file` control */
560
+ nile-file-preview {
561
+ display: block;
562
+ margin-top: 8px;
563
+ }
564
+
565
+ /* `test-connection-report` control */
566
+ .aq-mcp-report__meta {
567
+ margin: 0 0 10px;
568
+ font-size: 13px;
569
+ color: var(--ng-colors-text-tertiary-600, #667085);
570
+ }
571
+ .aq-mcp-report__checks {
572
+ margin-top: 16px;
573
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
574
+ border-radius: 12px;
575
+ overflow: hidden;
576
+ }
577
+ .aq-mcp-check {
578
+ border-top: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
579
+ }
580
+ .aq-mcp-check:first-child {
581
+ border-top: 0;
582
+ }
583
+ /* A plain check (no accordion): the row itself carries the padding. */
584
+ div.aq-mcp-check > .aq-mcp-check__row {
585
+ padding: 14px 16px;
586
+ }
587
+ /* A failed+mitigation check is a native <details>/<summary> — same row
588
+ markup/padding as the plain case, just with a trailing chevron and a
589
+ collapsible panel, so nothing shifts out of alignment between rows. */
590
+ details.aq-mcp-check > summary.aq-mcp-check__row {
591
+ padding: 14px 16px;
592
+ cursor: pointer;
593
+ list-style: none;
594
+ }
595
+ details.aq-mcp-check > summary::-webkit-details-marker {
596
+ display: none;
597
+ }
598
+ .aq-mcp-check__chevron {
599
+ flex: 0 0 auto;
600
+ margin-top: 1px;
601
+ color: var(--ng-colors-text-tertiary-600, #667085);
602
+ transition: transform 0.15s ease;
603
+ }
604
+ details[open] > summary .aq-mcp-check__chevron {
605
+ transform: rotate(180deg);
606
+ }
607
+ details.aq-mcp-check > .aq-mcp-check__fix {
608
+ margin: 0 16px 14px 16px;
609
+ }
610
+
611
+ .aq-mcp-check__row {
612
+ display: flex;
613
+ align-items: flex-start;
614
+ gap: 12px;
615
+ width: 100%;
616
+ }
617
+ .aq-mcp-check__icon {
618
+ flex: 0 0 auto;
619
+ margin-top: 1px;
620
+ }
621
+ .aq-mcp-check--passed .aq-mcp-check__icon {
622
+ color: var(--ng-color-success-500, #17b26a);
623
+ }
624
+ .aq-mcp-check--failed .aq-mcp-check__icon {
625
+ color: var(--ng-color-error-500, #d92d20);
626
+ }
627
+ .aq-mcp-check--skipped .aq-mcp-check__icon {
628
+ color: var(--ng-colors-text-tertiary-600, #98a2b3);
629
+ }
630
+ .aq-mcp-check__body {
631
+ flex: 1 1 auto;
632
+ min-width: 0;
633
+ }
634
+ .aq-mcp-check__name {
635
+ font-size: 14px;
636
+ font-weight: 600;
637
+ color: var(--ng-colors-text-secondary-700, #344054);
638
+ }
639
+ .aq-mcp-check__message {
640
+ margin-top: 2px;
641
+ font-size: 13px;
642
+ color: var(--ng-colors-text-tertiary-600, #667085);
643
+ }
644
+ .aq-mcp-check--failed .aq-mcp-check__message {
645
+ color: var(--ng-color-error-500, #d92d20);
646
+ }
647
+ .aq-mcp-check__endpoint {
648
+ flex: 0 0 auto;
649
+ padding-top: 2px;
650
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
651
+ font-size: 12px;
652
+ color: var(--ng-colors-text-tertiary-600, #667085);
653
+ white-space: nowrap;
654
+ }
655
+
656
+ /* The "How to fix" panel — a distinct card inside the accordion's content slot. */
657
+ .aq-mcp-check__fix {
658
+ background: var(--ng-colors-bg-secondary, #f9fafb);
659
+ border-radius: 8px;
660
+ padding: 12px 14px;
661
+ }
662
+ .aq-mcp-check__fix-label {
663
+ font-size: 11px;
664
+ font-weight: 700;
665
+ text-transform: uppercase;
666
+ letter-spacing: 0.04em;
667
+ color: var(--ng-colors-text-tertiary-600, #667085);
668
+ margin-bottom: 6px;
669
+ }
670
+ .aq-mcp-check__fix-body {
671
+ font-size: 13px;
672
+ line-height: 1.5;
673
+ color: var(--ng-colors-text-secondary-700, #344054);
674
+ }
675
+
676
+ /* `properties-table`'s own card header (title/subtitle + action toolbar) — this
677
+ control is a `FULL_WIDTH_CONTROLS` entry (aq-mcp-field.ts), so `.aq-mcp-field`'s
678
+ usual two-column grid collapses to one column for its row via this class. */
679
+ .aq-mcp-field__control--full {
680
+ grid-column: 1 / -1;
681
+ }
682
+
683
+ /* `permissions-report` control — a validation banner + "N Granted" stat + an
684
+ All/Success filter + a Resource Type/Permission Type/Access table, matching
685
+ the live reference's own Credentials-tab Test Connection result. The filter
686
+ is pure CSS: the two `.aq-mcp-permissions__filter-input` radios are visually
687
+ hidden but keep native tab/keyboard/click behavior; `:has()` lets the
688
+ ancestor `.aq-mcp-permissions__report` react to which one is checked without
689
+ any per-instance selector (`:nth-of-type(2)` = the "Success" radio, always
690
+ second) — no Lit reactive state needed for the toggle. */
691
+ .aq-mcp-permissions__filter-input {
692
+ position: absolute;
693
+ opacity: 0;
694
+ width: 1px;
695
+ height: 1px;
696
+ }
697
+ .aq-mcp-permissions__report {
698
+ margin-top: 16px;
699
+ }
700
+ .aq-mcp-permissions__report-title {
701
+ font-size: 15px;
702
+ font-weight: 700;
703
+ color: var(--ng-colors-text-primary-900, #101828);
704
+ margin-bottom: 12px;
705
+ }
706
+ .aq-mcp-permissions__toolbar {
707
+ display: flex;
708
+ align-items: center;
709
+ justify-content: space-between;
710
+ margin-bottom: 12px;
711
+ }
712
+ .aq-mcp-permissions__stat {
713
+ display: flex;
714
+ align-items: center;
715
+ gap: 8px;
716
+ padding-left: 12px;
717
+ border-left: 3px solid var(--ng-color-success-500, #12b76a);
718
+ }
719
+ .aq-mcp-permissions__stat-count {
720
+ font-size: 15px;
721
+ font-weight: 700;
722
+ color: var(--ng-colors-text-primary-900, #101828);
723
+ }
724
+ .aq-mcp-permissions__stat-label {
725
+ font-size: 12px;
726
+ color: var(--ng-colors-text-tertiary-600, #667085);
727
+ }
728
+ .aq-mcp-permissions__filter-labels {
729
+ display: flex;
730
+ padding: 2px;
731
+ background: var(--ng-colors-bg-secondary, #f9fafb);
732
+ border-radius: 8px;
733
+ }
734
+ .aq-mcp-permissions__filter-label {
735
+ padding: 5px 14px;
736
+ font-size: 13px;
737
+ font-weight: 600;
738
+ color: var(--ng-colors-text-tertiary-600, #667085);
739
+ border-radius: 6px;
740
+ cursor: pointer;
741
+ }
742
+ .aq-mcp-permissions__filter-input:checked + .aq-mcp-permissions__filter-label {
743
+ background: var(--ng-colors-bg-primary, #fff);
744
+ color: var(--ng-componentcolors-utility-brand-600, #155eef);
745
+ box-shadow: 0 1px 2px rgb(16 24 40 / 0.05);
746
+ }
747
+ .aq-mcp-permissions__filter-input:nth-of-type(2):checked ~ .aq-mcp-permissions__table
748
+ .aq-mcp-permissions__row[data-access]:not([data-access='Granted']) {
749
+ display: none;
750
+ }
751
+ .aq-mcp-table-card__header {
752
+ display: flex;
753
+ align-items: flex-start;
754
+ justify-content: space-between;
755
+ gap: 16px;
756
+ margin-bottom: 16px;
757
+ }
758
+ .aq-mcp-table-card__title {
759
+ font-size: 14px;
760
+ font-weight: 600;
761
+ color: var(--ng-colors-text-secondary-700, #344054);
762
+ }
763
+ .aq-mcp-table-card__subtitle {
764
+ margin-top: 4px;
765
+ font-size: 13px;
766
+ color: var(--ng-colors-text-tertiary-600, #667085);
767
+ }
768
+ .aq-mcp-table-card__actions {
769
+ display: flex;
770
+ align-items: center;
771
+ gap: 8px;
772
+ flex-shrink: 0;
773
+ }
774
+
775
+ /* `properties-table` control — a plain semantic <table>, not a Nile component
776
+ (Nile has no read-only data-table primitive this simple use case needs). */
777
+ .aq-mcp-table {
778
+ width: 100%;
779
+ border: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
780
+ border-radius: 12px;
781
+ border-collapse: collapse;
782
+ overflow: hidden;
783
+ font-size: 13px;
784
+ }
785
+ .aq-mcp-table th,
786
+ .aq-mcp-table td {
787
+ padding: 12px 16px;
788
+ text-align: left;
789
+ border-top: 1px solid var(--ng-colors-border-secondary, #e4e7ec);
790
+ }
791
+ .aq-mcp-table thead th {
792
+ border-top: 0;
793
+ background: var(--ng-colors-bg-secondary, #f9fafb);
794
+ font-size: 11px;
795
+ font-weight: 700;
796
+ text-transform: uppercase;
797
+ letter-spacing: 0.04em;
798
+ color: var(--ng-colors-text-tertiary-600, #667085);
799
+ }
800
+ .aq-mcp-table__property {
801
+ font-weight: 600;
802
+ color: var(--ng-colors-text-secondary-700, #344054);
803
+ white-space: nowrap;
804
+ max-width: 200px;
805
+ overflow: hidden;
806
+ text-overflow: ellipsis;
807
+ }
808
+ .aq-mcp-table__value {
809
+ color: var(--ng-colors-text-tertiary-600, #667085);
810
+ overflow-wrap: anywhere;
811
+ }