@adia-ai/web-modules 0.8.39 → 0.8.41

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 (120) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +6 -6
  3. package/agent-admin/admin-roster/admin-roster.a2ui.json +1 -0
  4. package/agent-admin/admin-roster/admin-roster.css +10 -2
  5. package/agent-admin/admin-roster/admin-roster.js +70 -22
  6. package/agent-admin/admin-settings/admin-settings.a2ui.json +1 -0
  7. package/agent-admin/agent-admin/agent-admin.a2ui.json +1 -0
  8. package/billing/billing-overview/billing-overview.a2ui.json +10 -1
  9. package/billing/billing-overview/billing-overview.class.js +142 -73
  10. package/billing/billing-overview/billing-overview.css +70 -57
  11. package/billing/billing-overview/billing-overview.yaml +5 -0
  12. package/billing/invoice-detail/invoice-detail.a2ui.json +9 -4
  13. package/billing/invoice-detail/invoice-detail.class.js +22 -5
  14. package/billing/invoice-detail/invoice-detail.yaml +9 -4
  15. package/billing/invoice-history/invoice-history.a2ui.json +7 -4
  16. package/billing/invoice-history/invoice-history.class.js +45 -12
  17. package/billing/invoice-history/invoice-history.css +18 -15
  18. package/billing/invoice-history/invoice-history.yaml +10 -4
  19. package/billing/payment-method-form/payment-method-form.a2ui.json +1 -0
  20. package/billing/payment-method-form/payment-method-form.class.js +3 -3
  21. package/billing/payment-method-form/payment-method-form.css +4 -4
  22. package/billing/payment-method-list/payment-method-list.a2ui.json +4 -0
  23. package/billing/payment-method-list/payment-method-list.class.js +31 -11
  24. package/billing/payment-method-list/payment-method-list.css +13 -10
  25. package/billing/payment-method-list/payment-method-list.yaml +3 -0
  26. package/billing/plan-picker/plan-picker.a2ui.json +6 -0
  27. package/billing/plan-picker/plan-picker.class.js +70 -22
  28. package/billing/plan-picker/plan-picker.css +21 -38
  29. package/billing/plan-picker/plan-picker.yaml +5 -0
  30. package/chat/chat-composer/chat-composer.a2ui.json +1 -0
  31. package/chat/chat-empty/chat-empty.a2ui.json +4 -0
  32. package/chat/chat-empty/chat-empty.yaml +2 -0
  33. package/chat/chat-header/chat-header.a2ui.json +5 -0
  34. package/chat/chat-header/chat-header.yaml +3 -0
  35. package/chat/chat-shell/chat-shell.a2ui.json +4 -0
  36. package/chat/chat-shell/chat-shell.yaml +2 -0
  37. package/chat/chat-shell/css/chat-shell.tokens.css +1 -1
  38. package/chat/chat-sidebar/chat-sidebar.a2ui.json +4 -0
  39. package/chat/chat-sidebar/chat-sidebar.yaml +2 -0
  40. package/chat/chat-status/chat-status.a2ui.json +4 -0
  41. package/chat/chat-status/chat-status.yaml +2 -0
  42. package/chat/chat-thread/chat-thread.a2ui.json +4 -0
  43. package/chat/chat-thread/chat-thread.yaml +2 -0
  44. package/dashboard/dashboard-layout/dashboard-layout.a2ui.json +8 -8
  45. package/dashboard/dashboard-layout/dashboard-layout.css +4 -4
  46. package/dashboard/dashboard-layout/dashboard-layout.js +10 -9
  47. package/dashboard/dashboard-layout/dashboard-layout.yaml +19 -7
  48. package/dashboard/date-range-selector/date-range-selector.a2ui.json +4 -3
  49. package/dashboard/date-range-selector/date-range-selector.yaml +6 -4
  50. package/dist/chat/chat-shell.min.css +1 -1
  51. package/dist/chat/chat-shell.min.js +25 -25
  52. package/dist/editor/editor-shell.min.js +3 -3
  53. package/dist/everything.min.js +118 -131
  54. package/dist/shell/admin-shell.min.css +1 -1
  55. package/dist/shell/admin-shell.min.js +6 -6
  56. package/dist/simple/simple-shell.min.js +5 -5
  57. package/dist/web-modules.min.css +1 -1
  58. package/dist/web-modules.sheet.js +1 -1
  59. package/editor/editor-canvas/editor-canvas.a2ui.json +4 -0
  60. package/editor/editor-canvas/editor-canvas.yaml +2 -0
  61. package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +4 -0
  62. package/editor/editor-canvas-empty/editor-canvas-empty.yaml +2 -0
  63. package/editor/editor-canvas-toolbar/editor-canvas-toolbar.a2ui.json +4 -0
  64. package/editor/editor-canvas-toolbar/editor-canvas-toolbar.yaml +2 -0
  65. package/editor/editor-shell/editor-shell.a2ui.json +5 -0
  66. package/editor/editor-shell/editor-shell.yaml +3 -0
  67. package/editor/editor-sidebar/editor-sidebar.a2ui.json +4 -0
  68. package/editor/editor-sidebar/editor-sidebar.yaml +2 -0
  69. package/editor/editor-statusbar/editor-statusbar.a2ui.json +4 -0
  70. package/editor/editor-statusbar/editor-statusbar.yaml +2 -0
  71. package/editor/editor-toolbar/editor-toolbar.a2ui.json +4 -0
  72. package/editor/editor-toolbar/editor-toolbar.yaml +2 -0
  73. package/feedback/confirm-dialog/confirm-dialog.a2ui.json +1 -0
  74. package/form/form-popover/form-popover.a2ui.json +1 -0
  75. package/onboarding/onboarding-checklist/onboarding-checklist.a2ui.json +2 -1
  76. package/onboarding/onboarding-checklist/onboarding-checklist.js +2 -2
  77. package/onboarding/onboarding-checklist/onboarding-checklist.yaml +1 -1
  78. package/package.json +6 -6
  79. package/runtime/a2ui-root/a2ui-root.a2ui.json +6 -5
  80. package/runtime/a2ui-root/a2ui-root.css +75 -0
  81. package/runtime/a2ui-root/a2ui-root.js +118 -10
  82. package/runtime/a2ui-root/a2ui-root.yaml +7 -2
  83. package/runtime/gen-root/gen-root.a2ui.json +6 -5
  84. package/runtime/gen-root/gen-root.yaml +10 -2
  85. package/settings/integrations-page/integrations-page.a2ui.json +3 -2
  86. package/settings/integrations-page/integrations-page.js +8 -8
  87. package/settings/integrations-page/integrations-page.yaml +2 -2
  88. package/settings/notification-preferences/notification-preferences.a2ui.json +1 -0
  89. package/shell/admin-command/admin-command.a2ui.json +5 -1
  90. package/shell/admin-command/admin-command.yaml +3 -1
  91. package/shell/admin-content/admin-content.a2ui.json +4 -0
  92. package/shell/admin-content/admin-content.yaml +2 -0
  93. package/shell/admin-entity-item/admin-entity-item.a2ui.json +1 -0
  94. package/shell/admin-page/admin-page.a2ui.json +5 -0
  95. package/shell/admin-page/admin-page.yaml +3 -0
  96. package/shell/admin-page-body/admin-page-body.a2ui.json +4 -0
  97. package/shell/admin-page-body/admin-page-body.yaml +2 -0
  98. package/shell/admin-page-footer/admin-page-footer.a2ui.json +4 -0
  99. package/shell/admin-page-footer/admin-page-footer.yaml +2 -0
  100. package/shell/admin-page-header/admin-page-header.a2ui.json +4 -0
  101. package/shell/admin-page-header/admin-page-header.yaml +2 -0
  102. package/shell/admin-scroll/admin-scroll.a2ui.json +4 -0
  103. package/shell/admin-scroll/admin-scroll.yaml +2 -0
  104. package/shell/admin-shell/admin-shell.a2ui.json +7 -3
  105. package/shell/admin-shell/admin-shell.yaml +3 -1
  106. package/shell/admin-shell/css/admin-shell.sidebar.css +2 -2
  107. package/shell/admin-sidebar/admin-sidebar.a2ui.json +4 -0
  108. package/shell/admin-sidebar/admin-sidebar.yaml +2 -0
  109. package/shell/admin-statusbar/admin-statusbar.a2ui.json +6 -0
  110. package/shell/admin-statusbar/admin-statusbar.yaml +4 -0
  111. package/shell/admin-topbar/admin-topbar.a2ui.json +6 -0
  112. package/shell/admin-topbar/admin-topbar.yaml +4 -0
  113. package/shell/embed-shell/embed-shell.a2ui.json +1 -0
  114. package/simple/simple-content/simple-content.a2ui.json +4 -0
  115. package/simple/simple-content/simple-content.yaml +2 -0
  116. package/simple/simple-hero/simple-hero.a2ui.json +4 -0
  117. package/simple/simple-hero/simple-hero.yaml +2 -0
  118. package/simple/simple-shell/simple-shell.a2ui.json +4 -0
  119. package/simple/simple-shell/simple-shell.yaml +2 -0
  120. package/theme/theme-panel/theme-panel.a2ui.json +1 -0
@@ -19,11 +19,19 @@
19
19
  * - stat-ui for the KPI strip
20
20
  *
21
21
  * Architecture (light-DOM, ADR-0033):
22
- * Stamps a six-region skeleton once on first connect:
23
- * [data-header] — plan title + status tag + slotted actions
24
- * [data-kpis] — 3-stat KPI strip
22
+ * Stamps a six-region skeleton once on first connect. Each region is
23
+ * the canonical settings-section grammar from
24
+ * apps/saas/app/billing/billing.contents.html (gh#1519):
25
+ * <section> → <aside> label rail (h3 + p) → <col-ui> content column
26
+ * The regions, in DOM order:
27
+ * [data-header] — current-plan card-ui (title + status tag
28
+ * + slotted actions) in a labeled section
29
+ * [data-kpis] — <grid-ui> of card-ui>section>stat-ui KPI
30
+ * cards + a seats <progress-ui> quota bar
25
31
  * [data-dunning] — alert-ui[pattern="dunning"], conditional
26
- * [data-plan-picker] — plan-picker-ui section, conditional
32
+ * [data-plan-picker] — plan-picker-ui (cycle-toggle on — the
33
+ * canonical field-ui billing-interval
34
+ * chain), conditional
27
35
  * [data-payment-methods] — payment-method-list-ui section
28
36
  * [data-invoice-history] — invoice-history-ui section
29
37
  * [data-empty] — default empty-state branch
@@ -138,6 +146,7 @@ export class UIBillingOverview extends UIElement {
138
146
  #kpisHost = null;
139
147
  #kpiSpend = null;
140
148
  #kpiSeats = null;
149
+ #kpiSeatsQuota = null;
141
150
  #kpiInvoice = null;
142
151
 
143
152
  // Dunning banner
@@ -148,6 +157,7 @@ export class UIBillingOverview extends UIElement {
148
157
  #planPickerHost = null;
149
158
  #planPickerHeading = null;
150
159
  #planPicker = null;
160
+ #forwardedCycle = '';
151
161
 
152
162
  // Payment-methods section
153
163
  #paymentMethodsHost = null;
@@ -322,15 +332,43 @@ export class UIBillingOverview extends UIElement {
322
332
  for (const n of nodes) target.appendChild(n);
323
333
  }
324
334
 
335
+ /**
336
+ * Stamp one region in the canonical settings-section grammar
337
+ * (apps/saas/app/billing/billing.contents.html): a <section> whose
338
+ * children are an <aside> label rail (h3 + p) and a <col-ui>
339
+ * content column. Returns [section, contentCol].
340
+ */
341
+ #stampRegion(marker, heading, description) {
342
+ const section = document.createElement('section');
343
+ section.setAttribute(marker, '');
344
+ const aside = document.createElement('aside');
345
+ const h3 = document.createElement('h3');
346
+ h3.setAttribute('data-section-heading', '');
347
+ h3.textContent = heading;
348
+ const p = document.createElement('p');
349
+ p.setAttribute('data-section-description', '');
350
+ p.textContent = description;
351
+ aside.append(h3, p);
352
+ const col = document.createElement('col-ui');
353
+ col.setAttribute('data-region-content', '');
354
+ col.setAttribute('gap', '3');
355
+ section.append(aside, col);
356
+ this.appendChild(section);
357
+ return [section, col];
358
+ }
359
+
325
360
  #stamp() {
326
361
  this.#headingId = `bo-h-${Math.random().toString(36).slice(2, 8)}`;
327
362
 
328
- // ── Header region ── card-ui wrapping plan title + status tag + actions.
329
- // Card chrome matches canonical billing.contents.html plan card pattern
330
- // (apps/saas/app/billing/billing.contents.html lines 13-29). See
331
- // .brain/findings/demos/billing-overview.wireframe.md §Level 5 tree.
363
+ // ── Header region ── canonical "Current plan" section: aside rail +
364
+ // col-ui column holding the card-ui plan card (billing.contents.html
365
+ // lines 26-62).
366
+ const [headerSection, headerCol] = this.#stampRegion(
367
+ 'data-header', 'Current plan', 'Your active subscription and billing interval.',
368
+ );
369
+
332
370
  const headerCard = document.createElement('card-ui');
333
- headerCard.setAttribute('data-header', '');
371
+ headerCard.setAttribute('data-plan-card', '');
334
372
 
335
373
  const header = document.createElement('header');
336
374
 
@@ -355,11 +393,13 @@ export class UIBillingOverview extends UIElement {
355
393
  statusBadge.setAttribute('size', 'md');
356
394
  headerHeadingSpan.appendChild(statusBadge);
357
395
 
358
- // Description slot — plan price/cycle/next-invoice meta.
396
+ // Description slot — plan price/cycle meta as a row-ui cluster.
359
397
  const headerDescriptionSpan = document.createElement('span');
360
398
  headerDescriptionSpan.setAttribute('slot', 'description');
361
- const subtitle = document.createElement('div');
399
+ const subtitle = document.createElement('row-ui');
362
400
  subtitle.setAttribute('data-billing-plan-meta', '');
401
+ subtitle.setAttribute('gap', '3');
402
+ subtitle.setAttribute('wrap', '');
363
403
  headerDescriptionSpan.appendChild(subtitle);
364
404
 
365
405
  // Action slot — change-plan + consumer-supplied actions.
@@ -371,9 +411,9 @@ export class UIBillingOverview extends UIElement {
371
411
 
372
412
  header.append(headerIconSpan, headerHeadingSpan, headerDescriptionSpan, headerActionSpan);
373
413
  headerCard.appendChild(header);
374
- this.appendChild(headerCard);
414
+ headerCol.appendChild(headerCard);
375
415
 
376
- this.#headerHost = headerCard;
416
+ this.#headerHost = headerSection;
377
417
  this.#headerTitle = title;
378
418
  this.#headerSubtitle = subtitle;
379
419
  this.#headerStatus = statusBadge;
@@ -382,16 +422,23 @@ export class UIBillingOverview extends UIElement {
382
422
  // Absorb consumer [slot="header-actions"] children.
383
423
  this.#absorbSlottedChildren('header-actions', headerActions);
384
424
 
385
- // ── KPI strip ── card-ui × 3 wrapping stat-ui (currentSpend · seats · nextInvoice).
386
- // Card chrome matches canonical KPI strip pattern from
387
- // apps/saas/app/admin-dashboard/admin-dashboard.contents.html (card-ui +
388
- // section + stat-ui). See wireframe Level 5 tree.
389
- const kpis = document.createElement('section');
390
- kpis.setAttribute('data-kpis', '');
425
+ // ── KPI strip ── canonical "Usage this cycle" section: a grid-ui of
426
+ // card-ui>section>stat-ui KPI cards (canonical stats grammar,
427
+ // billing.contents.html change-plan drawer) plus a seats
428
+ // progress-ui quota bar (canonical usage grammar, lines 78-83).
429
+ const [kpisSection, kpisCol] = this.#stampRegion(
430
+ 'data-kpis', 'Usage this cycle', 'Spend, seats, and your next invoice.',
431
+ );
432
+
433
+ const kpiGrid = document.createElement('grid-ui');
434
+ kpiGrid.setAttribute('data-kpi-grid', '');
435
+ kpiGrid.setAttribute('columns', '3');
436
+ kpiGrid.setAttribute('gap', '3');
437
+ kpisCol.appendChild(kpiGrid);
391
438
 
392
439
  /**
393
440
  * Helper: wrap a stat-ui in <card-ui><section> per canonical KPI pattern.
394
- * @returns {[HTMLElement, HTMLElement]} [cardWrapper, statElement]
441
+ * @returns {[HTMLElement, HTMLElement, HTMLElement]} [cardWrapper, statElement, cardSection]
395
442
  */
396
443
  const makeKpiCard = (kpiId, label, icon) => {
397
444
  const card = document.createElement('card-ui');
@@ -404,91 +451,92 @@ export class UIBillingOverview extends UIElement {
404
451
  stat.setAttribute('icon', icon);
405
452
  section.appendChild(stat);
406
453
  card.appendChild(section);
407
- return [card, stat];
454
+ return [card, stat, section];
408
455
  };
409
456
 
410
457
  const [kpiSpendCard, kpiSpend] = makeKpiCard('spend', 'Current month', 'credit-card');
411
- const [kpiSeatsCard, kpiSeats] = makeKpiCard('seats', 'Seats', 'users-three');
458
+ const [kpiSeatsCard, kpiSeats, kpiSeatsSection] = makeKpiCard('seats', 'Seats', 'users-three');
412
459
  const [kpiInvoiceCard, kpiInvoice] = makeKpiCard('next-invoice', 'Next invoice', 'calendar');
413
460
 
414
- kpis.append(kpiSpendCard, kpiSeatsCard, kpiInvoiceCard);
415
- this.appendChild(kpis);
416
- this.#kpisHost = kpis;
461
+ // Seats quota bar — progress-ui[label][meta] per the canonical usage
462
+ // section; hidden until account.seats carries a usable used/total.
463
+ const seatsQuota = document.createElement('progress-ui');
464
+ seatsQuota.setAttribute('data-seats-quota', '');
465
+ seatsQuota.setAttribute('label', 'Seats used');
466
+ seatsQuota.setAttribute('hidden', '');
467
+ kpiSeatsSection.appendChild(seatsQuota);
468
+
469
+ kpiGrid.append(kpiSpendCard, kpiSeatsCard, kpiInvoiceCard);
470
+ this.#kpisHost = kpisSection;
417
471
  this.#kpiSpend = kpiSpend;
418
472
  this.#kpiSeats = kpiSeats;
473
+ this.#kpiSeatsQuota = seatsQuota;
419
474
  this.#kpiInvoice = kpiInvoice;
420
475
 
421
- // ── Dunning banner ── alert-ui[pattern="dunning"], conditional.
422
- const dunningHost = document.createElement('section');
423
- dunningHost.setAttribute('data-dunning', '');
424
- dunningHost.setAttribute('hidden', '');
476
+ // ── Dunning banner ── alert-ui[pattern="dunning"], conditional; the
477
+ // whole labeled section hides for non-past-due accounts.
478
+ const [dunningSection, dunningCol] = this.#stampRegion(
479
+ 'data-dunning', 'Payment issue', 'Action needed to keep your subscription active.',
480
+ );
481
+ dunningSection.setAttribute('hidden', '');
425
482
  const dunningAlert = document.createElement('alert-ui');
426
483
  dunningAlert.setAttribute('pattern', 'dunning');
427
484
  dunningAlert.setAttribute('variant', 'danger');
428
- dunningHost.appendChild(dunningAlert);
429
- this.appendChild(dunningHost);
430
- this.#dunningHost = dunningHost;
485
+ dunningCol.appendChild(dunningAlert);
486
+ this.#dunningHost = dunningSection;
431
487
  this.#dunningAlert = dunningAlert;
432
488
 
433
- // ── Plan-picker section ── conditional on [variant=full].
434
- const planPickerHost = document.createElement('section');
435
- planPickerHost.setAttribute('data-plan-picker', '');
436
- const planPickerHeading = document.createElement('h3');
437
- planPickerHeading.setAttribute('data-section-heading', '');
438
- planPickerHeading.textContent = 'Plan';
439
- const planPicker = document.createElement('plan-picker-ui');
440
- planPicker.setAttribute('data-billing-plan-picker', '');
441
- planPicker.setAttribute('name', 'billing-overview-plan');
442
- planPickerHost.append(planPickerHeading, planPicker);
443
-
444
- // Optional before-plan-picker slot anchor sits before the plan picker
445
- // section card. We absorb consumer-authored content into planPickerHost
446
- // ahead of the heading.
447
- this.appendChild(planPickerHost);
448
- this.#planPickerHost = planPickerHost;
449
- this.#planPickerHeading = planPickerHeading;
450
- this.#planPicker = planPicker;
489
+ // ── Plan-picker section ── conditional on [variant=full]. The picker
490
+ // runs with [cycle-toggle] so its field-ui("Billing interval") +
491
+ // segmented-ui chain — the canonical current-plan region's interval
492
+ // switcher renders inline.
493
+ const [planPickerSection, planPickerCol] = this.#stampRegion(
494
+ 'data-plan-picker', 'Plan', 'Compare plans and switch anytime.',
495
+ );
451
496
 
452
- // Absorb before-plan-picker slot content into the section
453
- // ahead of the plan picker.
497
+ // Absorb before-plan-picker slot content ahead of the plan picker.
454
498
  const beforePlanPicker = document.createElement('div');
455
499
  beforePlanPicker.setAttribute('data-before-plan-picker', '');
456
- planPickerHost.insertBefore(beforePlanPicker, planPickerHeading);
500
+ planPickerCol.appendChild(beforePlanPicker);
457
501
  this.#absorbSlottedChildren('before-plan-picker', beforePlanPicker);
458
502
 
503
+ const planPicker = document.createElement('plan-picker-ui');
504
+ planPicker.setAttribute('data-billing-plan-picker', '');
505
+ planPicker.setAttribute('name', 'billing-overview-plan');
506
+ planPicker.setAttribute('cycle-toggle', '');
507
+ planPickerCol.appendChild(planPicker);
508
+
509
+ this.#planPickerHost = planPickerSection;
510
+ this.#planPickerHeading = planPickerSection.querySelector('[data-section-heading]');
511
+ this.#planPicker = planPicker;
512
+
459
513
  // ── Payment-methods section ──
460
- const paymentMethodsHost = document.createElement('section');
461
- paymentMethodsHost.setAttribute('data-payment-methods', '');
462
- const paymentMethodsHeading = document.createElement('h3');
463
- paymentMethodsHeading.setAttribute('data-section-heading', '');
464
- paymentMethodsHeading.textContent = 'Payment methods';
514
+ const [paymentMethodsSection, paymentMethodsCol] = this.#stampRegion(
515
+ 'data-payment-methods', 'Payment methods', 'Cards and wallets on file for recurring charges.',
516
+ );
465
517
  const paymentMethods = document.createElement('payment-method-list-ui');
466
518
  paymentMethods.setAttribute('data-billing-payment-methods', '');
467
519
  paymentMethods.setAttribute('name', 'billing-overview-payment-method');
468
- paymentMethodsHost.append(paymentMethodsHeading, paymentMethods);
469
- this.appendChild(paymentMethodsHost);
470
- this.#paymentMethodsHost = paymentMethodsHost;
471
- this.#paymentMethodsHeading = paymentMethodsHeading;
520
+ paymentMethodsCol.appendChild(paymentMethods);
521
+ this.#paymentMethodsHost = paymentMethodsSection;
522
+ this.#paymentMethodsHeading = paymentMethodsSection.querySelector('[data-section-heading]');
472
523
  this.#paymentMethods = paymentMethods;
473
524
 
474
525
  // ── Invoice-history section ──
475
- const invoiceHistoryHost = document.createElement('section');
476
- invoiceHistoryHost.setAttribute('data-invoice-history', '');
477
- const invoiceHistoryHeading = document.createElement('h3');
478
- invoiceHistoryHeading.setAttribute('data-section-heading', '');
479
- invoiceHistoryHeading.textContent = 'Invoice history';
526
+ const [invoiceHistorySection, invoiceHistoryCol] = this.#stampRegion(
527
+ 'data-invoice-history', 'Invoice history', 'Download past invoices for accounting.',
528
+ );
480
529
  const invoiceHistory = document.createElement('invoice-history-ui');
481
530
  invoiceHistory.setAttribute('data-billing-invoice-history', '');
482
- invoiceHistoryHost.append(invoiceHistoryHeading, invoiceHistory);
483
- this.appendChild(invoiceHistoryHost);
484
- this.#invoiceHistoryHost = invoiceHistoryHost;
485
- this.#invoiceHistoryHeading = invoiceHistoryHeading;
531
+ invoiceHistoryCol.appendChild(invoiceHistory);
532
+ this.#invoiceHistoryHost = invoiceHistorySection;
533
+ this.#invoiceHistoryHeading = invoiceHistorySection.querySelector('[data-section-heading]');
486
534
  this.#invoiceHistory = invoiceHistory;
487
535
 
488
536
  // After-invoice-history slot anchor.
489
537
  const afterInvoiceHistory = document.createElement('div');
490
538
  afterInvoiceHistory.setAttribute('data-after-invoice-history', '');
491
- invoiceHistoryHost.appendChild(afterInvoiceHistory);
539
+ invoiceHistoryCol.appendChild(afterInvoiceHistory);
492
540
  this.#absorbSlottedChildren('after-invoice-history', afterInvoiceHistory);
493
541
 
494
542
  // ── Empty state ──
@@ -582,7 +630,8 @@ export class UIBillingOverview extends UIElement {
582
630
  this.#kpiSpend.setAttribute('value', '—');
583
631
  }
584
632
 
585
- // KPI 2 — seats (used / total).
633
+ // KPI 2 — seats (used / total) + the seats quota bar (canonical
634
+ // usage-section progress-ui[label][meta] grammar).
586
635
  if (account?.seats && Number.isFinite(account.seats.used)) {
587
636
  const used = account.seats.used;
588
637
  const total = Number.isFinite(account.seats.total) ? account.seats.total : null;
@@ -590,8 +639,19 @@ export class UIBillingOverview extends UIElement {
590
639
  'value',
591
640
  total != null ? `${used} / ${total}` : String(used),
592
641
  );
642
+ if (this.#kpiSeatsQuota) {
643
+ if (total != null && total > 0) {
644
+ const pct = Math.max(0, Math.min(100, Math.round((used / total) * 100)));
645
+ this.#kpiSeatsQuota.setAttribute('value', String(pct));
646
+ this.#kpiSeatsQuota.setAttribute('meta', `${used} / ${total}`);
647
+ this.#kpiSeatsQuota.removeAttribute('hidden');
648
+ } else {
649
+ this.#kpiSeatsQuota.setAttribute('hidden', '');
650
+ }
651
+ }
593
652
  } else {
594
653
  this.#kpiSeats.setAttribute('value', '—');
654
+ if (this.#kpiSeatsQuota) this.#kpiSeatsQuota.setAttribute('hidden', '');
595
655
  }
596
656
 
597
657
  // KPI 3 — next invoice date.
@@ -683,6 +743,15 @@ export class UIBillingOverview extends UIElement {
683
743
  this.#planPicker.setAttribute('current', String(account.plan.id));
684
744
  }
685
745
 
746
+ // Seed the picker's billing cycle from account.billingCycle — only
747
+ // when the ACCOUNT's cycle changes, so a user toggle inside the
748
+ // picker isn't clobbered by unrelated re-renders (loading flips).
749
+ const cycle = account?.billingCycle === 'year' ? 'annual' : 'monthly';
750
+ if (this.#forwardedCycle !== cycle) {
751
+ this.#forwardedCycle = cycle;
752
+ this.#planPicker.setAttribute('cycle', cycle);
753
+ }
754
+
686
755
  if (this.loading) this.#planPicker.setAttribute('loading', '');
687
756
  else this.#planPicker.removeAttribute('loading');
688
757
  }
@@ -1,16 +1,21 @@
1
1
  /* ═══════════════════════════════════════════════════════════════
2
2
  <billing-overview-ui> — Module-tier billing dashboard composite.
3
3
 
4
- Stamps a stacked column of dashboard regions:
5
- [data-header] plan title + status badge + actions
6
- [data-kpis] 3-stat KPI strip (spend · seats · next)
4
+ Stamps a stacked column of dashboard regions, each in the canonical
5
+ settings-section grammar from apps/saas/app/billing/billing.contents.html
6
+ (gh#1519): <section> <aside> label rail (h3 + p) <col-ui> content.
7
+ [data-header] current-plan card (title + status + actions)
8
+ [data-kpis] grid-ui of card>section>stat KPI cards +
9
+ seats progress-ui quota bar
7
10
  [data-dunning] alert-ui[pattern=dunning], conditional
8
11
  [data-plan-picker] plan-picker-ui section
9
12
  [data-payment-methods] payment-method-list-ui section
10
13
  [data-invoice-history] invoice-history-ui section
11
14
  [data-empty] default empty-state branch
12
15
 
13
- SPEC: docs/specs/implementation-ready/SPEC-004-billing-overview.md
16
+ Layout primitives (col-ui / row-ui / grid-ui) own the stack/row/grid
17
+ geometry; this file styles only the region grid, the aside rail, and
18
+ text chrome. SPEC: docs/specs/implementation-ready/SPEC-004-billing-overview.md
14
19
  Two-block @scope pattern per docs/specs/component-token-contract.md.
15
20
  Zero raw color values (repo hard rule); rem for sizes ≥ 3px.
16
21
  ═══════════════════════════════════════════════════════════════ */
@@ -22,7 +27,9 @@ billing-overview-ui {
22
27
  --billing-overview-gap: var(--a-space-5);
23
28
  --billing-overview-px: var(--a-space-5);
24
29
  --billing-overview-py: var(--a-space-5);
25
- --billing-overview-section-radius: var(--a-radius-lg);
30
+
31
+ --billing-overview-rail-w: 14rem;
32
+ --billing-overview-rail-gap: var(--a-space-4);
26
33
 
27
34
  --billing-overview-header-gap: var(--a-space-3);
28
35
 
@@ -33,6 +40,9 @@ billing-overview-ui {
33
40
  --billing-overview-section-heading-size: var(--a-text-base, 1rem);
34
41
  --billing-overview-section-heading-weight: var(--a-weight-semibold, 600);
35
42
 
43
+ --billing-overview-section-description-fg: var(--a-fg-muted);
44
+ --billing-overview-section-description-size: var(--a-text-sm, 0.875rem);
45
+
36
46
  --billing-overview-title-size: var(--a-text-2xl, 1.5rem);
37
47
  --billing-overview-title-weight: var(--a-weight-bold, 700);
38
48
 
@@ -59,11 +69,46 @@ billing-overview-ui {
59
69
  container-name: billing-overview;
60
70
  }
61
71
 
62
- /* ── Header region ── card-ui owns layout; we just style inline content.
63
- Per v1.7.1 wireframe re-author (2026-05-24): header is now <card-ui> with
64
- <header> slot composition (icon | heading | description | action).
65
- card-ui's @scope provides the layout; we only style descendants. */
72
+ /* ── Region grammar ── every region section is an <aside> label rail
73
+ beside a <col-ui> content column (the canonical [data-section]
74
+ aside + data-col shape). */
75
+ :scope > section {
76
+ display: grid;
77
+ grid-template-columns: minmax(0, var(--billing-overview-rail-w)) minmax(0, 1fr);
78
+ column-gap: var(--billing-overview-gap);
79
+ row-gap: var(--billing-overview-rail-gap);
80
+ align-items: start;
81
+ }
82
+
83
+ :scope > section[hidden] {
84
+ display: none;
85
+ }
66
86
 
87
+ :scope > section > aside {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: var(--a-space-1);
91
+ }
92
+
93
+ :scope [data-section-heading] {
94
+ margin: 0;
95
+ font-size: var(--billing-overview-section-heading-size);
96
+ font-weight: var(--billing-overview-section-heading-weight);
97
+ color: var(--billing-overview-section-heading-fg);
98
+ }
99
+
100
+ :scope [data-section-description] {
101
+ margin: 0;
102
+ font-size: var(--billing-overview-section-description-size);
103
+ color: var(--billing-overview-section-description-fg);
104
+ }
105
+
106
+ :scope > section > [data-region-content] {
107
+ min-width: 0;
108
+ }
109
+
110
+ /* ── Header region ── card-ui owns the card layout; we style only the
111
+ inline heading/meta content. */
67
112
  :scope > [data-header] [data-billing-plan-name] {
68
113
  margin: 0;
69
114
  font-size: var(--billing-overview-title-size);
@@ -72,10 +117,8 @@ billing-overview-ui {
72
117
  line-height: 1.2;
73
118
  }
74
119
 
120
+ /* Plan meta — a <row-ui wrap>; row-ui owns the flex geometry. */
75
121
  :scope > [data-header] [data-billing-plan-meta] {
76
- display: flex;
77
- flex-wrap: wrap;
78
- gap: var(--a-space-3);
79
122
  font-size: var(--billing-overview-meta-size);
80
123
  color: var(--billing-overview-meta-fg);
81
124
  }
@@ -105,52 +148,27 @@ billing-overview-ui {
105
148
  display: none;
106
149
  }
107
150
 
108
- /* ── KPI strip ── card-ui × 3 wrapping stat-ui per canonical pattern.
109
- Per v1.7.1 wireframe re-author: each KPI is now <card-ui><section><stat-ui>
110
- instead of CSS-faked card chrome on raw stat-ui. */
111
- :scope > [data-kpis] {
112
- display: grid;
151
+ /* ── KPI strip ── a <grid-ui data-kpi-grid> of card-ui>section>stat-ui
152
+ cards; forward this composite's column/gap tokens into the grid. */
153
+ :scope grid-ui[data-kpi-grid] {
113
154
  grid-template-columns: var(--billing-overview-kpi-columns);
114
- gap: var(--billing-overview-kpi-gap);
155
+ --grid-gap: var(--billing-overview-kpi-gap);
115
156
  }
116
157
 
117
- /* card-ui chrome is provided by card-ui's own @scope; no fake card chrome here. */
118
-
119
- /* ── Dunning banner ── */
120
- :scope > [data-dunning] {
121
- display: block;
158
+ /* Seats quota bar under the seats stat (canonical usage grammar). */
159
+ :scope [data-seats-quota] {
160
+ margin-block-start: var(--a-space-2);
122
161
  }
123
-
124
- :scope > [data-dunning][hidden] {
162
+ :scope [data-seats-quota][hidden] {
125
163
  display: none;
126
164
  }
127
165
 
128
- /* ── Section host shape ── plan-picker, payment-methods, invoice-history */
129
- :scope > [data-plan-picker],
130
- :scope > [data-payment-methods],
131
- :scope > [data-invoice-history] {
132
- display: flex;
133
- flex-direction: column;
134
- gap: var(--a-space-3);
135
- background: var(--md-sys-color-neutral-background);
136
- border: 1px solid var(--md-sys-color-neutral-outline-variant);
137
- border-radius: var(--billing-overview-section-radius);
138
- padding: var(--a-space-4);
139
- }
140
-
141
- :scope > [data-plan-picker][hidden],
142
- :scope > [data-payment-methods][hidden],
143
- :scope > [data-invoice-history][hidden] {
166
+ /* ── Slot anchors ── */
167
+ :scope [data-before-plan-picker]:empty,
168
+ :scope [data-after-invoice-history]:empty {
144
169
  display: none;
145
170
  }
146
171
 
147
- :scope [data-section-heading] {
148
- margin: 0;
149
- font-size: var(--billing-overview-section-heading-size);
150
- font-weight: var(--billing-overview-section-heading-weight);
151
- color: var(--billing-overview-section-heading-fg);
152
- }
153
-
154
172
  /* ── Variant cascade ── */
155
173
  /* compact + enterprise hide the plan-picker section (also gated in JS). */
156
174
  :scope[variant="compact"] > [data-plan-picker],
@@ -158,12 +176,6 @@ billing-overview-ui {
158
176
  display: none;
159
177
  }
160
178
 
161
- /* ── Slot anchors ── */
162
- :scope [data-before-plan-picker]:empty,
163
- :scope [data-after-invoice-history]:empty {
164
- display: none;
165
- }
166
-
167
179
  /* ── Empty + loading states ── */
168
180
  :scope > [data-empty] {
169
181
  display: none;
@@ -208,12 +220,13 @@ billing-overview-ui {
208
220
  color: var(--a-danger-strong, var(--a-danger, var(--a-fg-muted)));
209
221
  }
210
222
 
211
- /* ── Container query — collapse KPI strip below ~640px ── */
223
+ /* ── Container query — stack region rails + collapse the KPI grid
224
+ below ~640px ── */
212
225
  @container billing-overview (max-width: 40rem) {
213
- :scope > [data-kpis] {
226
+ :scope > section {
214
227
  grid-template-columns: 1fr;
215
228
  }
216
- :scope > [data-header] {
229
+ :scope grid-ui[data-kpi-grid] {
217
230
  grid-template-columns: 1fr;
218
231
  }
219
232
  :scope > [data-header] [data-header-actions] {
@@ -50,6 +50,11 @@ composes:
50
50
  - tag-ui
51
51
  - text-ui
52
52
  - alert-ui
53
+ - avatar-ui
54
+ - col-ui
55
+ - row-ui
56
+ - grid-ui
57
+ - progress-ui
53
58
  - plan-picker-ui
54
59
  - payment-method-list-ui
55
60
  - invoice-history-ui
@@ -27,17 +27,21 @@
27
27
  "default": ""
28
28
  },
29
29
  "density": {
30
- "description": "Line-items table row density. `compact` for tightly-packed\ninvoices with many line items; `standard` for typical invoices.\nForwarded to the embedded <table-ui density>.\n",
30
+ "description": "Line-items table row density. `compact` for tightly-packed\ninvoices with many line items; unset (default) for typical\ninvoices. Forwarded to the embedded <table-ui density>.\nConverged onto the global `[density]` Enum·preset vocabulary\n(gh#1335) — `standard` retired (it was never a real state; the\nforwarding logic already treated it as unset) and `spacious`\nadded for parity with the global grammar.\n",
31
31
  "type": "string",
32
32
  "enum": [
33
+ "",
33
34
  "compact",
34
- "standard"
35
+ "spacious"
35
36
  ],
36
- "default": "standard"
37
+ "default": ""
37
38
  },
38
39
  "invoice": {
39
40
  "description": "The invoice record. Source of truth for the rendered page.\nShape: {number, status, issuedAt, dueAt, customer:{name,email},\nremitTo?:{name,address?}, lines:[{description,qty,unitAmount,\namount}], subtotal, tax, discount?, total, currency,\nnotes?:string}. Null + no data-stream-src renders the empty\nstate.\n",
40
- "type": "object",
41
+ "type": [
42
+ "object",
43
+ "null"
44
+ ],
41
45
  "default": null
42
46
  },
43
47
  "loading": {
@@ -167,6 +171,7 @@
167
171
  "void"
168
172
  ],
169
173
  "name": "UIInvoiceDetail",
174
+ "parts": {},
170
175
  "related": [
171
176
  "Card",
172
177
  "Table",
@@ -91,6 +91,17 @@ function formatDate(iso, locale) {
91
91
  }
92
92
  }
93
93
 
94
+ /**
95
+ * Translate invoice-detail-ui's own `density` onto table-ui's global
96
+ * [density] grammar (ADR-0054). gh#1335 — the vocabulary now IS the
97
+ * global one (compact/spacious/unset), so this is an identity passthrough
98
+ * for a real value; "" (or anything unrecognized) is the absent attribute.
99
+ * Returns null for "no attribute".
100
+ */
101
+ function mapDensity(density) {
102
+ return density === 'compact' || density === 'spacious' ? density : null;
103
+ }
104
+
94
105
  export class UIInvoiceDetail extends UIElement {
95
106
  static get properties() {
96
107
  return {
@@ -98,7 +109,7 @@ export class UIInvoiceDetail extends UIElement {
98
109
  invoice: { type: Object, default: null },
99
110
  status: { type: String, default: 'draft', reflect: true },
100
111
  currency: { type: String, default: 'USD', reflect: true },
101
- density: { type: String, default: 'standard', reflect: true },
112
+ density: { type: String, default: '', reflect: true },
102
113
  loading: { type: Boolean, default: false, reflect: true },
103
114
  printable: { type: Boolean, default: true, reflect: true },
104
115
  locale: { type: String, default: '' },
@@ -423,7 +434,10 @@ export class UIInvoiceDetail extends UIElement {
423
434
 
424
435
  const table = document.createElement('table-ui');
425
436
  table.setAttribute('data-lines-table', '');
426
- table.setAttribute('density', this.density || 'standard');
437
+ // table-ui has no local `density` prop (ADR-0054) — forward via the
438
+ // global attribute, translated through mapDensity().
439
+ const initialDensity = mapDensity(this.density);
440
+ if (initialDensity) table.setAttribute('density', initialDensity);
427
441
  linesBody.appendChild(table);
428
442
 
429
443
  lines.appendChild(linesCard);
@@ -599,9 +613,12 @@ export class UIInvoiceDetail extends UIElement {
599
613
  unitAmount: line?.unitAmount ?? 0,
600
614
  amount: line?.amount ?? 0,
601
615
  }));
602
- // Mirror density.
603
- if (this.#linesTable.getAttribute('density') !== this.density) {
604
- this.#linesTable.setAttribute('density', this.density || 'standard');
616
+ // Mirror density — table-ui has no local `density` prop (ADR-0054);
617
+ // forward via the global attribute, translated through mapDensity().
618
+ const mirroredDensity = mapDensity(this.density);
619
+ if (this.#linesTable.getAttribute('density') !== mirroredDensity) {
620
+ if (mirroredDensity) this.#linesTable.setAttribute('density', mirroredDensity);
621
+ else this.#linesTable.removeAttribute('density');
605
622
  }
606
623
  }
607
624