@vantageos/mosaic 0.3.1 → 0.5.0

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 (157) hide show
  1. package/CHANGELOG.md +88 -2
  2. package/LICENSE +110 -0
  3. package/README.md +193 -21
  4. package/dist/PipelineBoard-FucyGArR.d.cts +759 -0
  5. package/dist/PipelineBoard-FucyGArR.d.ts +759 -0
  6. package/dist/PipelineBoard.markdown-DIcvrS-2.d.cts +16 -0
  7. package/dist/PipelineBoard.markdown-DIcvrS-2.d.ts +16 -0
  8. package/dist/{StatusBadge.schema-1tOjB4Ir.d.cts → StatusBadge.schema-BEjSH2_4.d.cts} +2 -2
  9. package/dist/{StatusBadge.schema-1tOjB4Ir.d.ts → StatusBadge.schema-BEjSH2_4.d.ts} +2 -2
  10. package/dist/{Tabs.schema-BzcEyeq8.d.cts → Tabs.schema-Dqr6vwV3.d.cts} +2 -2
  11. package/dist/{Tabs.schema-BzcEyeq8.d.ts → Tabs.schema-Dqr6vwV3.d.ts} +2 -2
  12. package/dist/Timeline-Coo_3tA_.d.cts +114 -0
  13. package/dist/Timeline-Coo_3tA_.d.ts +114 -0
  14. package/dist/Timeline.markdown-C6UPDv1H.d.cts +4 -0
  15. package/dist/Timeline.markdown-C6UPDv1H.d.ts +4 -0
  16. package/dist/TransactionPreview-BzWDn8kE.d.cts +391 -0
  17. package/dist/TransactionPreview-BzWDn8kE.d.ts +391 -0
  18. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.cts +7 -0
  19. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.ts +7 -0
  20. package/dist/artifacts.cjs +190 -2
  21. package/dist/artifacts.cjs.map +1 -1
  22. package/dist/artifacts.js +190 -2
  23. package/dist/artifacts.js.map +1 -1
  24. package/dist/confirmation.cjs +728 -2
  25. package/dist/confirmation.cjs.map +1 -1
  26. package/dist/confirmation.d.cts +2 -1
  27. package/dist/confirmation.d.ts +2 -1
  28. package/dist/confirmation.js +726 -4
  29. package/dist/confirmation.js.map +1 -1
  30. package/dist/display.cjs +1555 -203
  31. package/dist/display.cjs.map +1 -1
  32. package/dist/display.d.cts +6 -229
  33. package/dist/display.d.ts +6 -229
  34. package/dist/display.js +1539 -206
  35. package/dist/display.js.map +1 -1
  36. package/dist/forms.cjs +139 -46
  37. package/dist/forms.cjs.map +1 -1
  38. package/dist/forms.d.cts +2 -2
  39. package/dist/forms.d.ts +2 -2
  40. package/dist/forms.js +139 -46
  41. package/dist/forms.js.map +1 -1
  42. package/dist/host.cjs +822 -0
  43. package/dist/host.cjs.map +1 -0
  44. package/dist/host.d.cts +287 -0
  45. package/dist/host.d.ts +287 -0
  46. package/dist/host.js +794 -0
  47. package/dist/host.js.map +1 -0
  48. package/dist/{index-CwXVgtGb.d.cts → index-BadRcCbm.d.cts} +28 -11
  49. package/dist/{index-CwXVgtGb.d.ts → index-BadRcCbm.d.ts} +28 -11
  50. package/dist/index.cjs +2202 -247
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +42 -8
  53. package/dist/index.d.ts +42 -8
  54. package/dist/index.js +2167 -251
  55. package/dist/index.js.map +1 -1
  56. package/dist/input.d.cts +2 -2
  57. package/dist/input.d.ts +2 -2
  58. package/dist/media.cjs +190 -2
  59. package/dist/media.cjs.map +1 -1
  60. package/dist/media.d.cts +1 -1
  61. package/dist/media.d.ts +1 -1
  62. package/dist/media.js +190 -2
  63. package/dist/media.js.map +1 -1
  64. package/dist/preact/artifacts.js +190 -2
  65. package/dist/preact/artifacts.js.map +1 -1
  66. package/dist/preact/confirmation.d.ts +321 -1
  67. package/dist/preact/confirmation.js +629 -4
  68. package/dist/preact/confirmation.js.map +1 -1
  69. package/dist/preact/display.d.ts +570 -31
  70. package/dist/preact/display.js +1248 -126
  71. package/dist/preact/display.js.map +1 -1
  72. package/dist/preact/forms.d.ts +32 -15
  73. package/dist/preact/forms.js +139 -46
  74. package/dist/preact/forms.js.map +1 -1
  75. package/dist/preact/host.d.ts +307 -0
  76. package/dist/preact/host.js +875 -0
  77. package/dist/preact/host.js.map +1 -0
  78. package/dist/preact/media.d.ts +37 -1
  79. package/dist/preact/media.js +423 -0
  80. package/dist/preact/media.js.map +1 -1
  81. package/dist/preact/progress.d.ts +92 -1
  82. package/dist/preact/progress.js +469 -3
  83. package/dist/preact/progress.js.map +1 -1
  84. package/dist/progress.cjs +525 -2
  85. package/dist/progress.cjs.map +1 -1
  86. package/dist/progress.d.cts +2 -1
  87. package/dist/progress.d.ts +2 -1
  88. package/dist/progress.js +522 -3
  89. package/dist/progress.js.map +1 -1
  90. package/dist/react/artifacts.cjs +190 -2
  91. package/dist/react/artifacts.cjs.map +1 -1
  92. package/dist/react/artifacts.js +190 -2
  93. package/dist/react/artifacts.js.map +1 -1
  94. package/dist/react/confirmation.cjs +630 -2
  95. package/dist/react/confirmation.cjs.map +1 -1
  96. package/dist/react/confirmation.d.cts +3 -3
  97. package/dist/react/confirmation.d.ts +3 -3
  98. package/dist/react/confirmation.js +629 -4
  99. package/dist/react/confirmation.js.map +1 -1
  100. package/dist/react/display.cjs +1137 -123
  101. package/dist/react/display.cjs.map +1 -1
  102. package/dist/react/display.d.cts +42 -14
  103. package/dist/react/display.d.ts +42 -14
  104. package/dist/react/display.js +1122 -126
  105. package/dist/react/display.js.map +1 -1
  106. package/dist/react/forms.cjs +139 -46
  107. package/dist/react/forms.cjs.map +1 -1
  108. package/dist/react/forms.d.cts +1 -1
  109. package/dist/react/forms.d.ts +1 -1
  110. package/dist/react/forms.js +139 -46
  111. package/dist/react/forms.js.map +1 -1
  112. package/dist/react/host.cjs +910 -0
  113. package/dist/react/host.cjs.map +1 -0
  114. package/dist/react/host.d.cts +24 -0
  115. package/dist/react/host.d.ts +24 -0
  116. package/dist/react/host.js +875 -0
  117. package/dist/react/host.js.map +1 -0
  118. package/dist/react/input.d.cts +1 -1
  119. package/dist/react/input.d.ts +1 -1
  120. package/dist/react/media.cjs +426 -0
  121. package/dist/react/media.cjs.map +1 -1
  122. package/dist/react/media.d.cts +19 -1
  123. package/dist/react/media.d.ts +19 -1
  124. package/dist/react/media.js +423 -0
  125. package/dist/react/media.js.map +1 -1
  126. package/dist/react/progress.cjs +471 -2
  127. package/dist/react/progress.cjs.map +1 -1
  128. package/dist/react/progress.d.cts +1 -1
  129. package/dist/react/progress.d.ts +1 -1
  130. package/dist/react/progress.js +469 -3
  131. package/dist/react/progress.js.map +1 -1
  132. package/dist/server/resource.cjs +151 -0
  133. package/dist/server/resource.cjs.map +1 -0
  134. package/dist/server/resource.d.cts +90 -0
  135. package/dist/server/resource.d.ts +90 -0
  136. package/dist/server/resource.js +147 -0
  137. package/dist/server/resource.js.map +1 -0
  138. package/dist/server.cjs +1100 -25
  139. package/dist/server.cjs.map +1 -1
  140. package/dist/server.d.cts +84 -5
  141. package/dist/server.d.ts +84 -5
  142. package/dist/server.js +1087 -27
  143. package/dist/server.js.map +1 -1
  144. package/dist/tokens.cjs +73 -22
  145. package/dist/tokens.cjs.map +1 -1
  146. package/dist/tokens.d.cts +23 -3
  147. package/dist/tokens.d.ts +23 -3
  148. package/dist/tokens.js +73 -22
  149. package/dist/tokens.js.map +1 -1
  150. package/package.json +250 -231
  151. package/registry.yaml +51 -2
  152. package/dist/Alert.schema-LPJE5gFP.d.cts +0 -71
  153. package/dist/Alert.schema-LPJE5gFP.d.ts +0 -71
  154. package/dist/ProgressBar.schema-CdVYx0Wq.d.cts +0 -22
  155. package/dist/ProgressBar.schema-CdVYx0Wq.d.ts +0 -22
  156. package/dist/{ConfirmDialog-B3CQQGmJ.d.cts → ConfirmDialog-BCtOcmHe.d.cts} +1 -1
  157. package/dist/{ConfirmDialog-B3CQQGmJ.d.ts → ConfirmDialog-BCtOcmHe.d.ts} +1 -1
package/dist/index.cjs CHANGED
@@ -35,6 +35,8 @@ var EN = {
35
35
  "TokenDisplayOnceModal.copied": "Copied",
36
36
  "StatusBadge.aria.status": "Status",
37
37
  "StatusBadge.error.invalidProps": "StatusBadge: invalid props",
38
+ "Badge.aria.label": "Badge",
39
+ "Badge.error.invalidProps": "Badge: invalid props",
38
40
  "EmptyState.error.invalidProps": "EmptyState: invalid props",
39
41
  "Toast.aria.close": "Close notification",
40
42
  "Toast.error.invalidProps": "Toast: invalid props",
@@ -104,7 +106,99 @@ var EN = {
104
106
  "Select.search.placeholder": "Search\u2026",
105
107
  "Select.empty": "No options available",
106
108
  "Select.error.required": "Select an option",
107
- "Select.error.invalidProps": "Select: invalid props"
109
+ "Select.error.invalidProps": "Select: invalid props",
110
+ "ModelContext.selection.one": "1 row selected",
111
+ "ModelContext.selection.many": "{n} rows selected",
112
+ "ModelContext.selection.none": "No rows selected",
113
+ "TableView.sort.by": "Sort by",
114
+ "TableView.select.row": "Select row",
115
+ "TableView.select.all": "Select all rows",
116
+ "TableView.loading": "Loading table",
117
+ "StatCard.title": "Statistic",
118
+ "StatCard.trend.up": "Up",
119
+ "StatCard.trend.down": "Down",
120
+ "StatCard.trend.flat": "Flat",
121
+ "StatCard.empty.message": "No statistic to display",
122
+ "StatCard.loading": "Loading statistic",
123
+ "StatCard.error.invalidProps": "StatCard: invalid props",
124
+ "BalanceCard.title": "Balance",
125
+ "BalanceCard.network": "Network",
126
+ "BalanceCard.testnet": "Testnet",
127
+ "BalanceCard.address": "Address",
128
+ "BalanceCard.contract": "Token contract",
129
+ "BalanceCard.value": "Value",
130
+ "BalanceCard.copy": "Copy address",
131
+ "BalanceCard.copied": "Address copied",
132
+ "BalanceCard.empty.message": "No balance to display",
133
+ "BalanceCard.loading": "Loading balance",
134
+ "BalanceCard.error.invalidProps": "BalanceCard: invalid props",
135
+ "Timeline.title": "Timeline",
136
+ "Timeline.status.done": "Done",
137
+ "Timeline.status.current": "In progress",
138
+ "Timeline.status.pending": "Pending",
139
+ "Timeline.status.blocked": "Blocked",
140
+ "Timeline.completed": "{n} completed",
141
+ "Timeline.more": "{n} more",
142
+ "Timeline.empty.message": "No steps to display",
143
+ "Timeline.loading": "Loading timeline",
144
+ "Timeline.error.invalidProps": "Timeline: invalid props",
145
+ "MessageFeed.title": "Messages",
146
+ "MessageFeed.unread": "Unread",
147
+ "MessageFeed.attachment": "Attachment",
148
+ "MessageFeed.showing": "Showing the latest {n} of {total}",
149
+ "MessageFeed.empty.message": "No messages",
150
+ "MessageFeed.loading": "Loading messages",
151
+ "MessageFeed.error.invalidProps": "MessageFeed: invalid props",
152
+ "PipelineBoard.title": "Pipeline",
153
+ "PipelineBoard.deals.one": "1 deal",
154
+ "PipelineBoard.deals.many": "{n} deals",
155
+ "PipelineBoard.total": "Total",
156
+ "PipelineBoard.stage.empty": "No deals",
157
+ "PipelineBoard.filter.label": "Show stages",
158
+ "PipelineBoard.summary.open": "Open full board",
159
+ "PipelineBoard.more": "{n} more stages",
160
+ "PipelineBoard.empty.message": "No pipeline to display",
161
+ "PipelineBoard.loading": "Loading pipeline",
162
+ "PipelineBoard.error.invalidProps": "PipelineBoard: invalid props",
163
+ "PipelineBoard.col.stage": "Stage",
164
+ "PipelineBoard.col.deals": "Deals",
165
+ "TransactionPreview.title": "Transaction preview",
166
+ "TransactionPreview.notice": "NOT SIGNED - preparation only",
167
+ "TransactionPreview.kind.transfer": "Transfer",
168
+ "TransactionPreview.kind.swap": "Swap",
169
+ "TransactionPreview.testnet": "Testnet",
170
+ "TransactionPreview.field.network": "Network",
171
+ "TransactionPreview.field.from": "From",
172
+ "TransactionPreview.field.to": "To",
173
+ "TransactionPreview.field.amount": "Amount",
174
+ "TransactionPreview.field.fee": "Network fee",
175
+ "TransactionPreview.field.sell": "Sell",
176
+ "TransactionPreview.field.buy": "Buy",
177
+ "TransactionPreview.field.minReceived": "Minimum received",
178
+ "TransactionPreview.field.route": "Route",
179
+ "TransactionPreview.field.priceImpact": "Price impact",
180
+ "TransactionPreview.field.expires": "Quote expires",
181
+ "TransactionPreview.priceImpact.high": "High price impact",
182
+ "TransactionPreview.simulation.success": "Simulation succeeded",
183
+ "TransactionPreview.simulation.failed": "Simulation failed",
184
+ "TransactionPreview.simulation.unavailable": "Simulation unavailable",
185
+ "TransactionPreview.warnings": "Warnings",
186
+ "TransactionPreview.handoff": "Open in wallet",
187
+ "TransactionPreview.empty.message": "No transaction to preview",
188
+ "TransactionPreview.loading": "Loading preview",
189
+ "TransactionPreview.error.invalidProps": "TransactionPreview: invalid props",
190
+ "ProgressBar.title": "Progress",
191
+ "ConfirmDialog.title": "Confirmation",
192
+ "ConfirmDialog.options": "Answers",
193
+ "TableView.title": "Data table",
194
+ "TableView.markdown.more": "{n} more rows",
195
+ "MarkdownRenderer.title": "Markdown content",
196
+ "TokenDisplayOnceModal.title": "Token (shown once)",
197
+ "StatusBadge.title": "Status",
198
+ "TokenDisplayOnceModal.notice.once": "The value is shown once, in the app view only.",
199
+ "TokenDisplayOnceModal.field.scope": "Scope",
200
+ "TokenDisplayOnceModal.field.expires": "Expires",
201
+ "TokenDisplayOnceModal.field.fingerprint": "Fingerprint"
108
202
  };
109
203
  var FR = {
110
204
  // ─── Non-forms components ──────────────────────────────────────────────────
@@ -127,6 +221,8 @@ var FR = {
127
221
  "TokenDisplayOnceModal.copied": "Copi\xE9",
128
222
  "StatusBadge.aria.status": "Statut",
129
223
  "StatusBadge.error.invalidProps": "StatusBadge : propri\xE9t\xE9s invalides",
224
+ "Badge.aria.label": "Badge",
225
+ "Badge.error.invalidProps": "Badge : propri\xE9t\xE9s invalides",
130
226
  "EmptyState.error.invalidProps": "EmptyState : propri\xE9t\xE9s invalides",
131
227
  "Toast.aria.close": "Fermer la notification",
132
228
  "Toast.error.invalidProps": "Toast : propri\xE9t\xE9s invalides",
@@ -196,7 +292,99 @@ var FR = {
196
292
  "Select.search.placeholder": "Rechercher\u2026",
197
293
  "Select.empty": "Aucune option disponible",
198
294
  "Select.error.required": "S\xE9lectionnez une option",
199
- "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides"
295
+ "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides",
296
+ "ModelContext.selection.one": "1 ligne s\xE9lectionn\xE9e",
297
+ "ModelContext.selection.many": "{n} lignes s\xE9lectionn\xE9es",
298
+ "ModelContext.selection.none": "Aucune ligne s\xE9lectionn\xE9e",
299
+ "TableView.sort.by": "Trier par",
300
+ "TableView.select.row": "S\xE9lectionner la ligne",
301
+ "TableView.select.all": "Tout s\xE9lectionner",
302
+ "TableView.loading": "Chargement du tableau",
303
+ "StatCard.title": "Statistique",
304
+ "StatCard.trend.up": "En hausse",
305
+ "StatCard.trend.down": "En baisse",
306
+ "StatCard.trend.flat": "Stable",
307
+ "StatCard.empty.message": "Aucune statistique \xE0 afficher",
308
+ "StatCard.loading": "Chargement de la statistique",
309
+ "StatCard.error.invalidProps": "StatCard : propri\xE9t\xE9s invalides",
310
+ "BalanceCard.title": "Solde",
311
+ "BalanceCard.network": "R\xE9seau",
312
+ "BalanceCard.testnet": "R\xE9seau de test",
313
+ "BalanceCard.address": "Adresse",
314
+ "BalanceCard.contract": "Contrat du jeton",
315
+ "BalanceCard.value": "Valeur",
316
+ "BalanceCard.copy": "Copier l'adresse",
317
+ "BalanceCard.copied": "Adresse copi\xE9e",
318
+ "BalanceCard.empty.message": "Aucun solde \xE0 afficher",
319
+ "BalanceCard.loading": "Chargement du solde",
320
+ "BalanceCard.error.invalidProps": "BalanceCard : propri\xE9t\xE9s invalides",
321
+ "Timeline.title": "Chronologie",
322
+ "Timeline.status.done": "Termin\xE9",
323
+ "Timeline.status.current": "En cours",
324
+ "Timeline.status.pending": "\xC0 venir",
325
+ "Timeline.status.blocked": "Bloqu\xE9",
326
+ "Timeline.completed": "{n} termin\xE9(s)",
327
+ "Timeline.more": "{n} de plus",
328
+ "Timeline.empty.message": "Aucune \xE9tape \xE0 afficher",
329
+ "Timeline.loading": "Chargement de la chronologie",
330
+ "Timeline.error.invalidProps": "Timeline : propri\xE9t\xE9s invalides",
331
+ "MessageFeed.title": "Messages",
332
+ "MessageFeed.unread": "Non lu",
333
+ "MessageFeed.attachment": "Pi\xE8ce jointe",
334
+ "MessageFeed.showing": "Affichage des {n} derniers sur {total}",
335
+ "MessageFeed.empty.message": "Aucun message",
336
+ "MessageFeed.loading": "Chargement des messages",
337
+ "MessageFeed.error.invalidProps": "MessageFeed : propri\xE9t\xE9s invalides",
338
+ "PipelineBoard.title": "Pipeline",
339
+ "PipelineBoard.deals.one": "1 affaire",
340
+ "PipelineBoard.deals.many": "{n} affaires",
341
+ "PipelineBoard.total": "Total",
342
+ "PipelineBoard.stage.empty": "Aucune affaire",
343
+ "PipelineBoard.filter.label": "Afficher les \xE9tapes",
344
+ "PipelineBoard.summary.open": "Ouvrir le tableau complet",
345
+ "PipelineBoard.more": "{n} \xE9tapes de plus",
346
+ "PipelineBoard.empty.message": "Aucun pipeline \xE0 afficher",
347
+ "PipelineBoard.loading": "Chargement du pipeline",
348
+ "PipelineBoard.error.invalidProps": "PipelineBoard : propri\xE9t\xE9s invalides",
349
+ "PipelineBoard.col.stage": "\xC9tape",
350
+ "PipelineBoard.col.deals": "Affaires",
351
+ "TransactionPreview.title": "Aper\xE7u de transaction",
352
+ "TransactionPreview.notice": "NON SIGN\xC9 - pr\xE9paration uniquement",
353
+ "TransactionPreview.kind.transfer": "Transfert",
354
+ "TransactionPreview.kind.swap": "\xC9change",
355
+ "TransactionPreview.testnet": "R\xE9seau de test",
356
+ "TransactionPreview.field.network": "R\xE9seau",
357
+ "TransactionPreview.field.from": "De",
358
+ "TransactionPreview.field.to": "\xC0",
359
+ "TransactionPreview.field.amount": "Montant",
360
+ "TransactionPreview.field.fee": "Frais de r\xE9seau",
361
+ "TransactionPreview.field.sell": "Vendre",
362
+ "TransactionPreview.field.buy": "Acheter",
363
+ "TransactionPreview.field.minReceived": "Minimum re\xE7u",
364
+ "TransactionPreview.field.route": "Itin\xE9raire",
365
+ "TransactionPreview.field.priceImpact": "Impact sur le prix",
366
+ "TransactionPreview.field.expires": "Expiration du devis",
367
+ "TransactionPreview.priceImpact.high": "Impact sur le prix \xE9lev\xE9",
368
+ "TransactionPreview.simulation.success": "Simulation r\xE9ussie",
369
+ "TransactionPreview.simulation.failed": "Simulation \xE9chou\xE9e",
370
+ "TransactionPreview.simulation.unavailable": "Simulation indisponible",
371
+ "TransactionPreview.warnings": "Avertissements",
372
+ "TransactionPreview.handoff": "Ouvrir dans le portefeuille",
373
+ "TransactionPreview.empty.message": "Aucune transaction \xE0 pr\xE9visualiser",
374
+ "TransactionPreview.loading": "Chargement de l'aper\xE7u",
375
+ "TransactionPreview.error.invalidProps": "TransactionPreview : propri\xE9t\xE9s invalides",
376
+ "ProgressBar.title": "Progression",
377
+ "ConfirmDialog.title": "Confirmation",
378
+ "ConfirmDialog.options": "R\xE9ponses",
379
+ "TableView.title": "Tableau de donn\xE9es",
380
+ "TableView.markdown.more": "{n} lignes de plus",
381
+ "MarkdownRenderer.title": "Contenu Markdown",
382
+ "TokenDisplayOnceModal.title": "Jeton (affich\xE9 une fois)",
383
+ "StatusBadge.title": "Statut",
384
+ "TokenDisplayOnceModal.notice.once": "La valeur n'est affich\xE9e qu'une fois, dans la vue de l'application uniquement.",
385
+ "TokenDisplayOnceModal.field.scope": "Port\xE9e",
386
+ "TokenDisplayOnceModal.field.expires": "Expire le",
387
+ "TokenDisplayOnceModal.field.fingerprint": "Empreinte"
200
388
  };
201
389
  var TABLE = {
202
390
  en: EN,
@@ -269,6 +457,353 @@ function ProgressBar(raw) {
269
457
  return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", className: "mosaic-progress-error", children: t("ProgressBar.error.invalidProps", pickLocale(raw)) });
270
458
  }
271
459
  }
460
+
461
+ // src/components/display/EmptyState.logic.ts
462
+ var ACTION_VARIANT_CLASSES = {
463
+ primary: "inline-flex items-center justify-center rounded-md bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-slate-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-900 disabled:pointer-events-none disabled:opacity-50",
464
+ secondary: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:bg-slate-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400 disabled:pointer-events-none disabled:opacity-50"
465
+ };
466
+ var EmptyStatePropsSchema = zod.z.object({
467
+ title: zod.z.string().min(1),
468
+ description: zod.z.string().optional(),
469
+ action: zod.z.object({
470
+ label: zod.z.string().min(1),
471
+ variant: zod.z.enum(["primary", "secondary"]).default("primary")
472
+ }).optional(),
473
+ locale: zod.z.enum(["en", "fr"]).default("en")
474
+ });
475
+ function validateEmptyStateProps(raw) {
476
+ return EmptyStatePropsSchema.parse(raw);
477
+ }
478
+ function EmptyStateInner({
479
+ icon,
480
+ title,
481
+ description,
482
+ action,
483
+ locale: _locale
484
+ }) {
485
+ const variant = action?.variant ?? "primary";
486
+ return /* @__PURE__ */ jsxRuntime.jsxs(
487
+ "section",
488
+ {
489
+ "aria-label": title,
490
+ className: "flex flex-col items-center justify-center gap-4 px-6 py-12 text-center",
491
+ children: [
492
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "text-slate-400", children: icon }),
493
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-semibold text-slate-900", children: title }),
494
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm leading-relaxed text-slate-500", children: description }),
495
+ action && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: action.onClick, className: ACTION_VARIANT_CLASSES[variant], children: action.label })
496
+ ]
497
+ }
498
+ );
499
+ }
500
+ function EmptyState(raw) {
501
+ try {
502
+ const parsed = validateEmptyStateProps(raw);
503
+ return /* @__PURE__ */ jsxRuntime.jsx(
504
+ EmptyStateInner,
505
+ {
506
+ ...parsed,
507
+ icon: raw.icon,
508
+ action: parsed.action && raw.action?.onClick ? { ...parsed.action, onClick: raw.action.onClick } : void 0
509
+ }
510
+ );
511
+ } catch (err) {
512
+ console.error("[EmptyState] Invalid props \u2014 component will not render.", err);
513
+ const locale = raw.locale === "fr" ? "fr" : "en";
514
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("EmptyState.error.invalidProps", locale) });
515
+ }
516
+ }
517
+
518
+ // src/components/display/Skeleton.logic.ts
519
+ function getAnimationClass(animation) {
520
+ switch (animation) {
521
+ case "pulse":
522
+ return "animate-pulse";
523
+ case "wave":
524
+ return "skeleton-wave";
525
+ case "none":
526
+ return "";
527
+ }
528
+ }
529
+ function getVariantClass(variant) {
530
+ switch (variant) {
531
+ case "rect":
532
+ return "rounded-md";
533
+ case "circle":
534
+ return "rounded-full";
535
+ case "text":
536
+ return "rounded";
537
+ }
538
+ }
539
+ var SKELETON_BG_CLASS = "bg-gray-200 dark:bg-gray-700";
540
+ function textLineKey(index) {
541
+ return `skeleton-line-${index}`;
542
+ }
543
+ function lastLineWidth(index, total, baseWidth) {
544
+ if (total > 1 && index === total - 1) {
545
+ return "75%";
546
+ }
547
+ return baseWidth;
548
+ }
549
+ var SkeletonPropsSchema = zod.z.object({
550
+ variant: zod.z.enum(["rect", "circle", "text"]).default("rect"),
551
+ width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
552
+ height: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
553
+ count: zod.z.number().int().min(1).max(20).default(1),
554
+ animation: zod.z.enum(["pulse", "wave", "none"]).default("pulse"),
555
+ locale: zod.z.enum(["en", "fr"]).default("en")
556
+ });
557
+ function validateSkeletonProps(raw) {
558
+ return SkeletonPropsSchema.parse(raw);
559
+ }
560
+ function resolveSkeletonDimensions(variant, width, height) {
561
+ const toCss = (v, fallback) => {
562
+ if (v === void 0) return fallback;
563
+ if (typeof v === "number") return `${v}px`;
564
+ return v;
565
+ };
566
+ if (variant === "circle") {
567
+ const size = width !== void 0 ? toCss(width, "40px") : height !== void 0 ? toCss(height, "40px") : "40px";
568
+ return { widthCss: size, heightCss: size };
569
+ }
570
+ if (variant === "text") {
571
+ return {
572
+ widthCss: toCss(width, "100%"),
573
+ heightCss: toCss(height, "16px")
574
+ };
575
+ }
576
+ return {
577
+ widthCss: toCss(width, "100%"),
578
+ heightCss: toCss(height, "100px")
579
+ };
580
+ }
581
+ function SkeletonInner({ variant, width, height, count, animation, locale }) {
582
+ const { widthCss, heightCss } = resolveSkeletonDimensions(variant, width, height);
583
+ const animClass = getAnimationClass(animation);
584
+ const variantClass = getVariantClass(variant);
585
+ const ariaLabel = t("Skeleton.aria.label", locale);
586
+ const baseClass = `${SKELETON_BG_CLASS} ${variantClass} ${animClass}`.trim();
587
+ if (variant === "text") {
588
+ return (
589
+ // biome-ignore lint/a11y/useSemanticElements: role="status" is the correct ARIA pattern for loading indicators; <output> conveys form output semantics which is incorrect here
590
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-label": ariaLabel, "aria-busy": "true", className: "flex flex-col gap-2", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
591
+ "span",
592
+ {
593
+ className: `block ${baseClass}`,
594
+ style: {
595
+ width: lastLineWidth(i, count, widthCss),
596
+ height: heightCss
597
+ },
598
+ "aria-hidden": "true"
599
+ },
600
+ textLineKey(i)
601
+ )) })
602
+ );
603
+ }
604
+ return /* @__PURE__ */ jsxRuntime.jsx(
605
+ "div",
606
+ {
607
+ role: "status",
608
+ "aria-label": ariaLabel,
609
+ "aria-busy": "true",
610
+ className: `block ${baseClass}`,
611
+ style: { width: widthCss, height: heightCss }
612
+ }
613
+ );
614
+ }
615
+ function Skeleton(raw) {
616
+ try {
617
+ const props = validateSkeletonProps(raw);
618
+ return /* @__PURE__ */ jsxRuntime.jsx(SkeletonInner, { ...props });
619
+ } catch {
620
+ const locale = raw.locale === "fr" ? "fr" : "en";
621
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "text-red-600 text-sm", children: t("Skeleton.error.invalidProps", locale) });
622
+ }
623
+ }
624
+
625
+ // src/components/shared/datetime.ts
626
+ function formatTimestamp(iso, locale, timeZone) {
627
+ const d = new Date(iso);
628
+ if (Number.isNaN(d.getTime())) return iso;
629
+ try {
630
+ return new Intl.DateTimeFormat(locale, {
631
+ dateStyle: "medium",
632
+ timeStyle: "short",
633
+ ...timeZone ? { timeZone } : {}
634
+ }).format(d);
635
+ } catch {
636
+ return iso;
637
+ }
638
+ }
639
+
640
+ // src/components/progress/Timeline.logic.ts
641
+ function visibleSteps(steps, compact) {
642
+ if (!compact || steps.length === 0) {
643
+ return { visible: [...steps], hiddenBefore: 0, hiddenAfter: 0 };
644
+ }
645
+ let anchor = steps.findIndex((s) => s.status === "current");
646
+ if (anchor < 0) anchor = steps.findIndex((s) => s.status !== "done");
647
+ if (anchor < 0) anchor = steps.length - 1;
648
+ const end = Math.min(anchor + 2, steps.length);
649
+ return {
650
+ visible: steps.slice(anchor, end),
651
+ hiddenBefore: anchor,
652
+ hiddenAfter: steps.length - end
653
+ };
654
+ }
655
+ var TimelineStepSchema = zod.z.object({
656
+ id: zod.z.string().optional(),
657
+ title: zod.z.string().min(1),
658
+ status: zod.z.enum(["done", "current", "pending", "blocked"]),
659
+ description: zod.z.string().optional(),
660
+ /** ISO 8601. */
661
+ timestamp: zod.z.string().optional()
662
+ });
663
+ var TimelinePropsSchema = zod.z.object({
664
+ steps: zod.z.array(TimelineStepSchema).default([]),
665
+ orientation: zod.z.enum(["vertical", "horizontal"]).default("vertical"),
666
+ compact: zod.z.boolean().default(false),
667
+ /** IANA timezone for timestamps (e.g. `hostContext.timeZone`). Default: the viewer's. */
668
+ timeZone: zod.z.string().optional(),
669
+ loading: zod.z.boolean().default(false),
670
+ locale: zod.z.enum(["en", "fr"]).default("en")
671
+ });
672
+ function validateTimelineProps(raw) {
673
+ return TimelinePropsSchema.parse(raw);
674
+ }
675
+ var MARKER = { done: "\u2713", current: "\u25CF", pending: "\u25CB", blocked: "!" };
676
+ var MARKER_TONE = {
677
+ done: "text-green-700",
678
+ current: "text-blue-700",
679
+ pending: "text-slate-400",
680
+ blocked: "text-red-700"
681
+ };
682
+ function Timeline(raw = {}) {
683
+ const parsed = TimelinePropsSchema.safeParse(raw);
684
+ const locale = raw?.locale === "fr" ? "fr" : "en";
685
+ if (!parsed.success) {
686
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("Timeline.error.invalidProps", locale) });
687
+ }
688
+ const p = parsed.data;
689
+ if (p.loading) {
690
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("Timeline.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "text", count: 4, locale: p.locale }) });
691
+ }
692
+ if (p.steps.length === 0) {
693
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("Timeline.empty.message", p.locale), locale: p.locale });
694
+ }
695
+ const { visible, hiddenBefore, hiddenAfter } = visibleSteps(p.steps, p.compact);
696
+ const horizontal = p.orientation === "horizontal";
697
+ return /* @__PURE__ */ jsxRuntime.jsxs(
698
+ "section",
699
+ {
700
+ "aria-label": t("Timeline.title", p.locale),
701
+ lang: p.locale,
702
+ className: "flex flex-col gap-2",
703
+ children: [
704
+ hiddenBefore > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("Timeline.completed", p.locale).replace("{n}", String(hiddenBefore)) }) : null,
705
+ /* @__PURE__ */ jsxRuntime.jsx(
706
+ "ol",
707
+ {
708
+ "data-orientation": p.orientation,
709
+ className: `m-0 flex list-none gap-3 p-0 ${horizontal ? "flex-row overflow-x-auto" : "flex-col"}`,
710
+ style: {
711
+ scrollPaddingLeft: "var(--mosaic-safe-area-left, 0px)",
712
+ scrollPaddingRight: "var(--mosaic-safe-area-right, 0px)"
713
+ },
714
+ children: visible.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs(
715
+ "li",
716
+ {
717
+ "aria-current": s.status === "current" ? "step" : void 0,
718
+ className: "flex min-w-0 gap-2",
719
+ children: [
720
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: `font-bold ${MARKER_TONE[s.status]}`, children: MARKER[s.status] }),
721
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
722
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: s.title }),
723
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-slate-500", children: t(`Timeline.status.${s.status}`, p.locale) }),
724
+ s.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-600", children: s.description }) : null,
725
+ s.timestamp ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-slate-500", children: formatTimestamp(s.timestamp, p.locale, p.timeZone) }) : null
726
+ ] })
727
+ ]
728
+ },
729
+ s.id ?? `${s.title}-${i}`
730
+ ))
731
+ }
732
+ ),
733
+ hiddenAfter > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("Timeline.more", p.locale).replace("{n}", String(hiddenAfter)) }) : null
734
+ ]
735
+ }
736
+ );
737
+ }
738
+
739
+ // src/components/shared/markdown.ts
740
+ var SPECIAL = /([\\`*_[\]()<>&!|~])/g;
741
+ var BREAKS = /* @__PURE__ */ new Set([10, 11, 12, 13, 133, 8232, 8233]);
742
+ function flatten(text) {
743
+ let out = "";
744
+ let gap = false;
745
+ for (const c of text) {
746
+ const n = c.codePointAt(0) ?? 0;
747
+ if (BREAKS.has(n)) {
748
+ gap = true;
749
+ continue;
750
+ }
751
+ if (n < 32 && n !== 9) continue;
752
+ if (n === 127) continue;
753
+ if (gap) out += " ";
754
+ gap = false;
755
+ out += c;
756
+ }
757
+ return gap ? `${out} ` : out;
758
+ }
759
+ function mdEscape(text) {
760
+ const flat = flatten(text).replace(SPECIAL, "\\$1").replace(/\b(https?|ftp):\/\//gi, "$1:\\/\\/").replace(/\bwww\./gi, (m) => `${m.slice(0, 3)}\\.`);
761
+ return flat.replace(/^(\s*)([#>+-])/, "$1\\$2").replace(/^(\s*\d+)([.)])(?=\s|$)/, "$1\\$2");
762
+ }
763
+ function mdUrl(href) {
764
+ return href.replace(
765
+ /[()[\]\\<>`\s]/g,
766
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`
767
+ );
768
+ }
769
+ function mdCode(text) {
770
+ const flat = flatten(text);
771
+ const longest = Math.max(0, ...(flat.match(/`+/g) ?? []).map((r) => r.length));
772
+ const fence = "`".repeat(longest + 1);
773
+ return `${fence} ${flat} ${fence}`;
774
+ }
775
+ function mdTable(headers, rows) {
776
+ const line = (cells) => `| ${cells.join(" | ")} |`;
777
+ return [line(headers), line(headers.map(() => "---")), ...rows.map(line)].join("\n");
778
+ }
779
+ function mdHeading(text, level = 1) {
780
+ return `${"#".repeat(level)} ${text}`;
781
+ }
782
+
783
+ // src/components/progress/Timeline.markdown.ts
784
+ function timelineToMarkdown(props, locale = "en") {
785
+ const parsed = TimelinePropsSchema.safeParse(props);
786
+ const title = mdHeading(t("Timeline.title", locale));
787
+ if (!parsed.success || parsed.data.steps.length === 0) {
788
+ return `${title}
789
+
790
+ ${t("Timeline.empty.message", locale)}
791
+ `;
792
+ }
793
+ const { steps, timeZone } = parsed.data;
794
+ const lines = steps.map((s, i) => {
795
+ const status = t(`Timeline.status.${s.status}`, locale);
796
+ const extras = [
797
+ s.description ? mdEscape(s.description) : "",
798
+ s.timestamp ? mdEscape(formatTimestamp(s.timestamp, locale, timeZone)) : ""
799
+ ].filter(Boolean);
800
+ return `${i + 1}. [${status}] ${mdEscape(s.title)}${extras.length ? ` - ${extras.join(" - ")}` : ""}`;
801
+ });
802
+ return `${title}
803
+
804
+ ${lines.join("\n")}
805
+ `;
806
+ }
272
807
  var ConfirmDialogPropsSchema = zod.z.object({
273
808
  title: zod.z.string().min(1),
274
809
  message: zod.z.string().min(1),
@@ -375,92 +910,172 @@ var TabsPropsSchema = zod.z.object({
375
910
  function validateTabsProps(raw) {
376
911
  return TabsPropsSchema.parse(raw);
377
912
  }
913
+
914
+ // src/components/display/TableView.logic.ts
915
+ function isMissing(v) {
916
+ return v === null || v === void 0 || v === "";
917
+ }
918
+ var DECIMAL = /^(-)?(\d+)(?:\.(\d+))?$/;
919
+ function parseDecimal(s) {
920
+ const m = DECIMAL.exec(s.trim());
921
+ if (!m) return void 0;
922
+ const int = (m[2] ?? "0").replace(/^0+(?=\d)/, "");
923
+ const frac = (m[3] ?? "").replace(/0+$/, "");
924
+ const zero = int === "0" && frac === "";
925
+ return { neg: m[1] === "-" && !zero, int, frac };
926
+ }
927
+ function compareDecimalStrings(a, b) {
928
+ const x = parseDecimal(a);
929
+ const y = parseDecimal(b);
930
+ if (!x || !y) return 0;
931
+ if (x.neg !== y.neg) return x.neg ? -1 : 1;
932
+ const sign = x.neg ? -1 : 1;
933
+ if (x.int.length !== y.int.length) return sign * (x.int.length < y.int.length ? -1 : 1);
934
+ if (x.int !== y.int) return sign * (x.int < y.int ? -1 : 1);
935
+ const len = Math.max(x.frac.length, y.frac.length);
936
+ const fx = x.frac.padEnd(len, "0");
937
+ const fy = y.frac.padEnd(len, "0");
938
+ if (fx === fy) return 0;
939
+ return sign * (fx < fy ? -1 : 1);
940
+ }
941
+ var collators = /* @__PURE__ */ new Map();
942
+ function collator(locale) {
943
+ let c = collators.get(locale);
944
+ if (!c) {
945
+ c = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
946
+ collators.set(locale, c);
947
+ }
948
+ return c;
949
+ }
950
+ function compareCells(a, b, kind, locale) {
951
+ if (kind === "amount") return compareDecimalStrings(String(a), String(b));
952
+ if (kind === "number") {
953
+ const x = Number(a);
954
+ const y = Number(b);
955
+ if (Number.isNaN(x) || Number.isNaN(y)) return collator(locale).compare(String(a), String(b));
956
+ return x === y ? 0 : x < y ? -1 : 1;
957
+ }
958
+ return collator(locale).compare(String(a), String(b));
959
+ }
960
+ function sortItems(items, getValue, kind, direction, locale) {
961
+ const sign = direction === "asc" ? 1 : -1;
962
+ return items.map((item, index) => ({ item, index, value: getValue(item) })).sort((p, q) => {
963
+ const pm = isMissing(p.value);
964
+ const qm = isMissing(q.value);
965
+ if (pm || qm) return pm && qm ? p.index - q.index : pm ? 1 : -1;
966
+ const c = compareCells(p.value, q.value, kind, locale);
967
+ return c !== 0 ? sign * c : p.index - q.index;
968
+ }).map((e) => e.item);
969
+ }
970
+ function nextSort(current, key) {
971
+ if (!current || current.key !== key) return { key, direction: "asc" };
972
+ return current.direction === "asc" ? { key, direction: "desc" } : void 0;
973
+ }
974
+ function toggleSelection(current, key, mode) {
975
+ if (mode === "none") return new Set(current);
976
+ if (mode === "single") return current.has(key) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([key]);
977
+ const next = new Set(current);
978
+ if (!next.delete(key)) next.add(key);
979
+ return next;
980
+ }
981
+ function toggleAll(current, allKeys) {
982
+ return allKeys.length > 0 && allKeys.every((k) => current.has(k)) ? /* @__PURE__ */ new Set() : new Set(allKeys);
983
+ }
984
+ var ColumnKindSchema = zod.z.enum(["text", "number", "amount", "icon"]);
985
+ var AlignSchema = zod.z.enum(["start", "center", "end"]);
378
986
  var ColumnDefSchema = zod.z.object({
379
987
  key: zod.z.string().min(1),
380
- header: zod.z.string().min(1)
988
+ header: zod.z.string().min(1),
989
+ /** Cell kind: drives comparison and rendering. `amount` = decimal STRING, never a float. */
990
+ type: ColumnKindSchema.default("text"),
991
+ /** Defaults to `end` for `number` / `amount` columns, `start` otherwise. */
992
+ align: AlignSchema.optional(),
993
+ /** CSS width: a number is pixels. */
994
+ width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
995
+ sortable: zod.z.boolean().default(false),
996
+ /**
997
+ * Values of this column are masked in the A-1 markdown fallback and kept out of the HTML
998
+ * props of a tool result (delivered via the result `_meta`), never written to `content[]`.
999
+ */
1000
+ sensitive: zod.z.boolean().default(false)
381
1001
  // render is a runtime function — excluded from Zod, typed separately
382
1002
  });
1003
+ var SortSchema = zod.z.object({ key: zod.z.string().min(1), direction: zod.z.enum(["asc", "desc"]) });
383
1004
  var TableViewPropsSchema = zod.z.object({
384
1005
  columns: zod.z.array(ColumnDefSchema).min(1, "TableView requires at least one column"),
1006
+ /**
1007
+ * Serialisable rows, for views built through `createMosaicResource` (the React props carry
1008
+ * `rows` separately).
1009
+ */
1010
+ rows: zod.z.array(zod.z.record(zod.z.unknown())).default([]),
385
1011
  virtualizeThreshold: zod.z.number().int().positive().default(100),
386
1012
  ariaLabel: zod.z.string().min(1),
387
- locale: zod.z.enum(["en", "fr"]).default("en")
1013
+ locale: zod.z.enum(["en", "fr"]).default("en"),
1014
+ selectionMode: zod.z.enum(["none", "single", "multiple"]).default("none"),
1015
+ /** Initial sort. */
1016
+ sort: SortSchema.optional(),
1017
+ stickyHeader: zod.z.boolean().default(false),
1018
+ /** Scroll region height when `stickyHeader` is on (pixels). */
1019
+ maxHeight: zod.z.number().int().positive().default(400),
1020
+ density: zod.z.enum(["comfortable", "compact"]).default("comfortable"),
1021
+ loading: zod.z.boolean().default(false)
388
1022
  });
389
1023
  function validateTableViewProps(raw) {
390
1024
  return TableViewPropsSchema.parse(raw);
391
1025
  }
392
1026
  var OVERSCAN = 5;
393
- function TableView(props) {
394
- let validatedThreshold = 100;
1027
+ var SKELETON_ROWS = ["head", "r1", "r2", "r3", "r4", "r5"];
1028
+ function resolveLocale(l) {
1029
+ return l === "fr" ? "fr" : "en";
1030
+ }
1031
+ function validate(props) {
395
1032
  try {
396
1033
  const validated = validateTableViewProps({
397
- columns: props.columns.map(({ key, header }) => ({ key, header })),
1034
+ columns: props.columns.map(({ key, header, type, align, width, sortable }) => ({
1035
+ key,
1036
+ header,
1037
+ type,
1038
+ align,
1039
+ width,
1040
+ sortable
1041
+ })),
398
1042
  virtualizeThreshold: props.virtualizeThreshold,
399
1043
  ariaLabel: props.ariaLabel,
400
- locale: props.locale
1044
+ locale: props.locale,
1045
+ selectionMode: props.selectionMode,
1046
+ density: props.density
401
1047
  });
402
- validatedThreshold = validated.virtualizeThreshold;
1048
+ return validated.virtualizeThreshold;
403
1049
  } catch {
404
- const locale = props.locale === "fr" ? "fr" : "en";
405
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
1050
+ return void 0;
406
1051
  }
407
- const isVirtual = props.rows.length > validatedThreshold;
408
- const resolvedLocale = props.locale === "fr" ? "fr" : "en";
409
- if (isVirtual) {
410
- return /* @__PURE__ */ jsxRuntime.jsx(
411
- VirtualTable,
412
- {
413
- rows: props.rows,
414
- columns: props.columns,
415
- ariaLabel: props.ariaLabel,
416
- locale: resolvedLocale,
417
- onRowClick: props.onRowClick
418
- }
419
- );
1052
+ }
1053
+ function TableView(props) {
1054
+ const threshold = validate(props);
1055
+ if (threshold === void 0) {
1056
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
420
1057
  }
421
1058
  return /* @__PURE__ */ jsxRuntime.jsx(
422
- StaticTable,
1059
+ TableCore,
423
1060
  {
1061
+ ...props,
424
1062
  rows: props.rows,
425
- columns: props.columns,
426
- ariaLabel: props.ariaLabel,
427
- locale: resolvedLocale,
428
- onRowClick: props.onRowClick
1063
+ virtualizeThreshold: threshold,
1064
+ locale: resolveLocale(props.locale)
429
1065
  }
430
1066
  );
431
1067
  }
432
1068
  function StreamingTableView(props) {
433
- let validatedThreshold = 100;
434
- try {
435
- const validated = validateTableViewProps({
436
- columns: props.columns.map(({ key, header }) => ({ key, header })),
437
- virtualizeThreshold: props.virtualizeThreshold,
438
- ariaLabel: props.ariaLabel,
439
- locale: props.locale
440
- });
441
- validatedThreshold = validated.virtualizeThreshold;
442
- } catch {
443
- const locale = props.locale === "fr" ? "fr" : "en";
444
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
1069
+ const threshold = validate(props);
1070
+ if (threshold === void 0) {
1071
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
445
1072
  }
446
- return /* @__PURE__ */ jsxRuntime.jsx(
447
- StreamingTableViewInner,
448
- {
449
- ...props,
450
- virtualizeThreshold: validatedThreshold,
451
- onRowClick: props.onRowClick
452
- }
453
- );
1073
+ return /* @__PURE__ */ jsxRuntime.jsx(StreamingTableViewInner, { ...props, virtualizeThreshold: threshold });
454
1074
  }
455
1075
  function StreamingTableViewInner({
456
- columns,
457
1076
  rows$,
458
- virtualizeThreshold = 100,
459
- ariaLabel,
460
- locale,
461
- onRowClick
1077
+ ...rest
462
1078
  }) {
463
- const resolvedLocale = locale === "fr" ? "fr" : "en";
464
1079
  const [rows, setRows] = react.useState([]);
465
1080
  react.useEffect(() => {
466
1081
  setRows([]);
@@ -471,87 +1086,282 @@ function StreamingTableViewInner({
471
1086
  sub.unsubscribe();
472
1087
  };
473
1088
  }, [rows$]);
474
- const isVirtual = rows.length > virtualizeThreshold;
475
- if (isVirtual) {
1089
+ return /* @__PURE__ */ jsxRuntime.jsx(TableCore, { ...rest, rows, locale: resolveLocale(rest.locale) });
1090
+ }
1091
+ function kindOf(col) {
1092
+ return col.type ?? "text";
1093
+ }
1094
+ function alignOf(col) {
1095
+ return col.align ?? (col.type === "amount" || col.type === "number" ? "end" : "start");
1096
+ }
1097
+ function cellText(row, key) {
1098
+ return String(row[key] ?? "");
1099
+ }
1100
+ function safeIconSrc(v) {
1101
+ if (typeof v !== "string") return void 0;
1102
+ return /^(https?:\/\/|data:image\/)/i.test(v) ? v : void 0;
1103
+ }
1104
+ function renderCell(col, row) {
1105
+ if (col.render) {
1106
+ return col.render(row);
1107
+ }
1108
+ const value = row[col.key];
1109
+ if (col.type === "icon") {
1110
+ const src = safeIconSrc(value);
1111
+ return src ? /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: "", width: 20, height: 20, style: { verticalAlign: "middle" } }) : null;
1112
+ }
1113
+ if (col.type === "amount" || col.type === "number") {
1114
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: String(value ?? "") });
1115
+ }
1116
+ return String(value ?? "");
1117
+ }
1118
+ function TableCore(props) {
1119
+ const {
1120
+ columns,
1121
+ rows,
1122
+ ariaLabel,
1123
+ locale,
1124
+ selectionMode = "none",
1125
+ stickyHeader = false,
1126
+ density = "comfortable"
1127
+ } = props;
1128
+ const [innerSort, setInnerSort] = react.useState(props.defaultSort);
1129
+ const sort = props.sort === void 0 ? innerSort : props.sort ?? void 0;
1130
+ const onSort = (key) => {
1131
+ const next = nextSort(sort, key);
1132
+ if (props.sort === void 0) setInnerSort(next);
1133
+ props.onSortChange?.(next);
1134
+ };
1135
+ const items = react.useMemo(
1136
+ () => rows.map((row, i) => {
1137
+ const id = row.id;
1138
+ return { row, key: id != null ? String(id) : `row-${i}` };
1139
+ }),
1140
+ [rows]
1141
+ );
1142
+ const sorted = react.useMemo(() => {
1143
+ const col = sort ? columns.find((c) => c.key === sort.key) : void 0;
1144
+ if (!sort || !col) return items;
1145
+ return sortItems(
1146
+ items,
1147
+ (it) => it.row[col.key],
1148
+ kindOf(col),
1149
+ sort.direction,
1150
+ locale
1151
+ );
1152
+ }, [items, sort, columns, locale]);
1153
+ const [innerSelected, setInnerSelected] = react.useState(
1154
+ () => new Set(props.defaultSelectedKeys ?? [])
1155
+ );
1156
+ const selected = props.selectedKeys ? new Set(props.selectedKeys) : innerSelected;
1157
+ const commit = (next) => {
1158
+ if (!props.selectedKeys) setInnerSelected(next);
1159
+ const chosen = sorted.filter((it) => next.has(it.key));
1160
+ props.onSelectionChange?.(
1161
+ chosen.map((it) => it.row),
1162
+ chosen.map((it) => it.key)
1163
+ );
1164
+ };
1165
+ const selection = selectionMode === "none" ? void 0 : {
1166
+ mode: selectionMode,
1167
+ selected,
1168
+ toggle: (key) => commit(toggleSelection(selected, key, selectionMode)),
1169
+ toggleAll: () => commit(
1170
+ toggleAll(
1171
+ selected,
1172
+ sorted.map((it) => it.key)
1173
+ )
1174
+ )
1175
+ };
1176
+ if (props.loading) {
1177
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("TableView.loading", locale), lang: locale, children: SKELETON_ROWS.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1178
+ Skeleton,
1179
+ {
1180
+ variant: "rect",
1181
+ height: i === 0 ? 24 : 36,
1182
+ locale
1183
+ },
1184
+ SKELETON_ROWS[i]
1185
+ )) });
1186
+ }
1187
+ const content = {
1188
+ items: sorted,
1189
+ columns,
1190
+ ariaLabel,
1191
+ locale,
1192
+ onRowClick: props.onRowClick,
1193
+ sort,
1194
+ onSort,
1195
+ selection,
1196
+ sticky: stickyHeader,
1197
+ density
1198
+ };
1199
+ const isVirtual = rows.length > props.virtualizeThreshold;
1200
+ if (isVirtual) return /* @__PURE__ */ jsxRuntime.jsx(VirtualTable, { ...content });
1201
+ if (stickyHeader) {
476
1202
  return /* @__PURE__ */ jsxRuntime.jsx(
477
- VirtualTable,
1203
+ "div",
478
1204
  {
479
- rows,
480
- columns,
481
- ariaLabel,
482
- locale: resolvedLocale,
483
- onRowClick
1205
+ tabIndex: 0,
1206
+ role: "region",
1207
+ "aria-label": ariaLabel,
1208
+ style: { maxHeight: props.maxHeight ?? 400, overflow: "auto" },
1209
+ children: /* @__PURE__ */ jsxRuntime.jsx(StaticTable, { ...content })
484
1210
  }
485
1211
  );
486
1212
  }
1213
+ return /* @__PURE__ */ jsxRuntime.jsx(StaticTable, { ...content });
1214
+ }
1215
+ var CELL_PADDING = { comfortable: void 0, compact: "2px 6px" };
1216
+ function HeaderRow({
1217
+ columns,
1218
+ locale,
1219
+ sort,
1220
+ onSort,
1221
+ selection,
1222
+ sticky,
1223
+ density,
1224
+ items
1225
+ }) {
1226
+ const padding = CELL_PADDING[density];
1227
+ const stickyStyle = sticky ? { position: "sticky", top: 0, background: "var(--mosaic-color-background, Canvas)" } : {};
1228
+ const allSelected = items.length > 0 && items.every((it) => selection?.selected.has(it.key));
1229
+ const someSelected = items.some((it) => selection?.selected.has(it.key));
1230
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1231
+ selection ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", style: { ...stickyStyle, padding, width: 32 }, children: selection.mode === "multiple" ? /* @__PURE__ */ jsxRuntime.jsx(
1232
+ "input",
1233
+ {
1234
+ type: "checkbox",
1235
+ "aria-label": t("TableView.select.all", locale),
1236
+ checked: allSelected,
1237
+ ref: (el) => {
1238
+ if (el) el.indeterminate = someSelected && !allSelected;
1239
+ },
1240
+ onChange: selection.toggleAll
1241
+ }
1242
+ ) : null }) : null,
1243
+ columns.map((col) => {
1244
+ const dir = sort?.key === col.key ? sort.direction : void 0;
1245
+ return /* @__PURE__ */ jsxRuntime.jsx(
1246
+ "th",
1247
+ {
1248
+ scope: "col",
1249
+ "aria-sort": col.sortable ? dir === "asc" ? "ascending" : dir === "desc" ? "descending" : "none" : void 0,
1250
+ style: {
1251
+ ...stickyStyle,
1252
+ padding,
1253
+ textAlign: alignOf(col),
1254
+ ...col.width !== void 0 ? { width: col.width } : {}
1255
+ },
1256
+ children: col.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
1257
+ "button",
1258
+ {
1259
+ type: "button",
1260
+ "aria-label": `${t("TableView.sort.by", locale)} ${col.header}`,
1261
+ onClick: () => onSort(col.key),
1262
+ style: { all: "inherit", cursor: "pointer", padding: 0 },
1263
+ children: [
1264
+ col.header,
1265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: dir === "asc" ? " \u25B2" : dir === "desc" ? " \u25BC" : "" })
1266
+ ]
1267
+ }
1268
+ ) : col.header
1269
+ },
1270
+ col.key
1271
+ );
1272
+ })
1273
+ ] });
1274
+ }
1275
+ function SelectCell({
1276
+ item,
1277
+ columns,
1278
+ selection,
1279
+ locale,
1280
+ padding
1281
+ }) {
1282
+ const first = columns[0];
1283
+ const label = `${t("TableView.select.row", locale)}: ${first ? cellText(item.row, first.key) : item.key}`;
487
1284
  return /* @__PURE__ */ jsxRuntime.jsx(
488
- StaticTable,
1285
+ "td",
489
1286
  {
490
- rows,
491
- columns,
492
- ariaLabel,
493
- locale: resolvedLocale,
494
- onRowClick
1287
+ style: { padding, width: 32 },
1288
+ onClick: (e) => e.stopPropagation(),
1289
+ onKeyDown: (e) => e.stopPropagation(),
1290
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1291
+ "input",
1292
+ {
1293
+ type: selection.mode === "multiple" ? "checkbox" : "radio",
1294
+ name: selection.mode === "single" ? "mosaic-table-selection" : void 0,
1295
+ "aria-label": label,
1296
+ checked: selection.selected.has(item.key),
1297
+ onChange: () => selection.toggle(item.key),
1298
+ onKeyDown: (e) => e.stopPropagation()
1299
+ }
1300
+ )
495
1301
  }
496
1302
  );
497
1303
  }
498
- function renderCell(col, row) {
499
- if (col.render) {
500
- return col.render(row);
501
- }
502
- return String(row[col.key] ?? "");
1304
+ function rowActivation(onRowClick, row, index) {
1305
+ if (!onRowClick) return {};
1306
+ return {
1307
+ role: "button",
1308
+ tabIndex: 0,
1309
+ onClick: () => onRowClick(row, index),
1310
+ onKeyDown: (e) => {
1311
+ if (e.key === "Enter" || e.key === " ") {
1312
+ e.preventDefault();
1313
+ onRowClick(row, index);
1314
+ }
1315
+ }
1316
+ };
503
1317
  }
504
- function StaticTable({
505
- rows,
506
- columns,
507
- ariaLabel,
508
- locale,
509
- onRowClick
510
- }) {
1318
+ function StaticTable(props) {
1319
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
511
1320
  const emptyMsg = t("TableView.empty.message", locale);
1321
+ const padding = CELL_PADDING[density];
1322
+ const colSpan = columns.length + (selection ? 1 : 0);
512
1323
  return /* @__PURE__ */ jsxRuntime.jsxs(
513
1324
  "table",
514
1325
  {
515
1326
  "aria-label": ariaLabel,
516
- "aria-rowcount": rows.length,
1327
+ "aria-rowcount": items.length,
1328
+ "data-density": density,
517
1329
  style: { width: "100%", borderCollapse: "collapse" },
518
1330
  children: [
519
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
520
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: columns.length, "aria-label": emptyMsg, children: emptyMsg }) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
1331
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderRow, { ...props }) }),
1332
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan, "aria-label": emptyMsg, children: emptyMsg }) }) : items.map((item, rowIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
521
1333
  "tr",
522
1334
  {
523
1335
  "aria-rowindex": rowIndex + 2,
524
- ...onRowClick ? {
525
- role: "button",
526
- tabIndex: 0,
527
- style: { cursor: "pointer" },
528
- onClick: () => onRowClick(row, rowIndex),
529
- onKeyDown: (e) => {
530
- if (e.key === "Enter" || e.key === " ") {
531
- e.preventDefault();
532
- onRowClick(row, rowIndex);
1336
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
1337
+ ...onRowClick ? { style: { cursor: "pointer" } } : {},
1338
+ ...rowActivation(onRowClick, item.row, rowIndex),
1339
+ children: [
1340
+ selection ? /* @__PURE__ */ jsxRuntime.jsx(
1341
+ SelectCell,
1342
+ {
1343
+ item,
1344
+ columns,
1345
+ selection,
1346
+ locale,
1347
+ padding
533
1348
  }
534
- }
535
- } : {},
536
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(col, row) }, col.key))
1349
+ ) : null,
1350
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1351
+ ]
537
1352
  },
538
- row.id != null ? String(row.id) : `row-${rowIndex}`
1353
+ item.key
539
1354
  )) })
540
1355
  ]
541
1356
  }
542
1357
  );
543
1358
  }
544
- function VirtualTable({
545
- rows,
546
- columns,
547
- ariaLabel,
548
- // locale reserved for future virtual-mode empty/pagination strings
549
- locale: _locale,
550
- onRowClick
551
- }) {
1359
+ function VirtualTable(props) {
1360
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
552
1361
  const scrollRef = react.useRef(null);
1362
+ const padding = CELL_PADDING[density];
553
1363
  const rowVirtualizer = reactVirtual.useVirtualizer({
554
- count: rows.length,
1364
+ count: items.length,
555
1365
  /* v8 ignore next 1 */
556
1366
  getScrollElement: () => scrollRef.current,
557
1367
  estimateSize: react.useCallback(() => 40, []),
@@ -564,18 +1374,19 @@ function VirtualTable({
564
1374
  {
565
1375
  "data-virtual": "true",
566
1376
  ref: scrollRef,
567
- tabIndex: 0,
568
1377
  role: "region",
1378
+ tabIndex: 0,
569
1379
  "aria-label": ariaLabel,
570
1380
  style: { height: 400, overflow: "auto", position: "relative" },
571
1381
  children: /* @__PURE__ */ jsxRuntime.jsxs(
572
1382
  "table",
573
1383
  {
574
1384
  "aria-label": ariaLabel,
575
- "aria-rowcount": rows.length,
1385
+ "aria-rowcount": items.length,
1386
+ "data-density": density,
576
1387
  style: { width: "100%", borderCollapse: "collapse" },
577
1388
  children: [
578
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
1389
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderRow, { ...props, sticky: true }) }),
579
1390
  /* @__PURE__ */ jsxRuntime.jsx(
580
1391
  "tbody",
581
1392
  {
@@ -585,11 +1396,12 @@ function VirtualTable({
585
1396
  display: "block"
586
1397
  },
587
1398
  children: virtualItems.map((virtualRow) => {
588
- const row = rows[virtualRow.index] ?? {};
589
- return /* @__PURE__ */ jsxRuntime.jsx(
1399
+ const item = items[virtualRow.index] ?? { row: {}, key: "" };
1400
+ return /* @__PURE__ */ jsxRuntime.jsxs(
590
1401
  "tr",
591
1402
  {
592
1403
  "aria-rowindex": virtualRow.index + 2,
1404
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
593
1405
  style: {
594
1406
  position: "absolute",
595
1407
  top: 0,
@@ -599,20 +1411,22 @@ function VirtualTable({
599
1411
  display: "flex",
600
1412
  ...onRowClick ? { cursor: "pointer" } : {}
601
1413
  },
602
- ...onRowClick ? {
603
- role: "button",
604
- tabIndex: 0,
605
- onClick: () => onRowClick(row, virtualRow.index),
606
- onKeyDown: (e) => {
607
- if (e.key === "Enter" || e.key === " ") {
608
- e.preventDefault();
609
- onRowClick(row, virtualRow.index);
1414
+ ...rowActivation(onRowClick, item.row, virtualRow.index),
1415
+ children: [
1416
+ selection ? /* @__PURE__ */ jsxRuntime.jsx(
1417
+ SelectCell,
1418
+ {
1419
+ item,
1420
+ columns,
1421
+ selection,
1422
+ locale,
1423
+ padding
610
1424
  }
611
- }
612
- } : {},
613
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1 }, children: renderCell(col, row) }, col.key))
1425
+ ) : null,
1426
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1, padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1427
+ ]
614
1428
  },
615
- row.id != null ? String(row.id) : `vrow-${virtualRow.index}`
1429
+ item.key || `vrow-${virtualRow.index}`
616
1430
  );
617
1431
  })
618
1432
  }
@@ -623,124 +1437,767 @@ function VirtualTable({
623
1437
  }
624
1438
  );
625
1439
  }
626
- var EmptyStatePropsSchema = zod.z.object({
627
- title: zod.z.string().min(1),
628
- description: zod.z.string().optional(),
629
- action: zod.z.object({
630
- label: zod.z.string().min(1),
631
- variant: zod.z.enum(["primary", "secondary"]).default("primary")
632
- }).optional(),
633
- locale: zod.z.enum(["en", "fr"]).default("en")
634
- });
635
- function validateEmptyStateProps(raw) {
636
- return EmptyStatePropsSchema.parse(raw);
1440
+ var StatCardPropsSchema = zod.z.object({
1441
+ label: zod.z.string().default(""),
1442
+ value: zod.z.string().default(""),
1443
+ unit: zod.z.string().optional(),
1444
+ trend: zod.z.enum(["up", "down", "flat"]).optional(),
1445
+ /** Pre-formatted change, e.g. "+4.2%". */
1446
+ change: zod.z.string().optional(),
1447
+ tone: zod.z.enum(["neutral", "success", "warning", "danger"]).default("neutral"),
1448
+ description: zod.z.string().optional(),
1449
+ loading: zod.z.boolean().default(false),
1450
+ locale: zod.z.enum(["en", "fr"]).default("en")
1451
+ });
1452
+ function validateStatCardProps(raw) {
1453
+ return StatCardPropsSchema.parse(raw);
1454
+ }
1455
+ var TONE_CLASSES = {
1456
+ neutral: "text-slate-900",
1457
+ success: "text-green-700",
1458
+ warning: "text-amber-700",
1459
+ danger: "text-red-700"
1460
+ };
1461
+ var TREND_ARROW = { up: "\u25B2", down: "\u25BC", flat: "\u25AC" };
1462
+ function StatCard(raw) {
1463
+ const parsed = StatCardPropsSchema.safeParse(raw);
1464
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1465
+ if (!parsed.success) {
1466
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("StatCard.error.invalidProps", locale) });
1467
+ }
1468
+ const p = parsed.data;
1469
+ if (p.loading) {
1470
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("StatCard.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rect", height: 72, locale: p.locale }) });
1471
+ }
1472
+ if (p.label === "" && p.value === "") {
1473
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("StatCard.empty.message", p.locale), locale: p.locale });
1474
+ }
1475
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1476
+ "section",
1477
+ {
1478
+ "aria-label": `${t("StatCard.title", p.locale)}: ${p.label}`,
1479
+ lang: p.locale,
1480
+ className: "flex flex-col gap-1 rounded-lg border border-slate-200 bg-white p-4",
1481
+ children: [
1482
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: p.label }),
1483
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-2xl font-semibold tabular-nums ${TONE_CLASSES[p.tone]}`, children: [
1484
+ p.value,
1485
+ p.unit ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-base font-normal text-slate-500", children: p.unit }) : null
1486
+ ] }),
1487
+ p.trend || p.change ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-sm text-slate-600", children: [
1488
+ p.trend ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1489
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: TREND_ARROW[p.trend] }),
1490
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t(`StatCard.trend.${p.trend}`, p.locale) })
1491
+ ] }) : null,
1492
+ p.change ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: p.change }) : null
1493
+ ] }) : null,
1494
+ p.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: p.description }) : null
1495
+ ]
1496
+ }
1497
+ );
1498
+ }
1499
+
1500
+ // src/components/display/StatCard.markdown.ts
1501
+ function statCardToMarkdown(props, locale = "en") {
1502
+ const parsed = StatCardPropsSchema.safeParse(props);
1503
+ const title = mdHeading(t("StatCard.title", locale));
1504
+ if (!parsed.success || parsed.data.label === "" && parsed.data.value === "") {
1505
+ return `${title}
1506
+
1507
+ ${t("StatCard.empty.message", locale)}
1508
+ `;
1509
+ }
1510
+ const { label, value, unit, trend, change, description } = parsed.data;
1511
+ const amount = unit ? `${value} ${unit}` : value;
1512
+ const movement = [trend ? t(`StatCard.trend.${trend}`, locale) : "", change ?? ""].filter(Boolean).join(" ");
1513
+ const line = `- **${mdEscape(label)}**: ${mdEscape(amount)}${movement ? ` (${mdEscape(movement)})` : ""}`;
1514
+ return `${title}
1515
+
1516
+ ${line}
1517
+ ${description ? `
1518
+ ${mdEscape(description)}
1519
+ ` : ""}`;
1520
+ }
1521
+ var BadgePropsSchema = zod.z.object({
1522
+ label: zod.z.string(),
1523
+ variant: zod.z.enum(["default", "primary", "success", "warning", "danger", "info", "neutral"]).default("default"),
1524
+ dot: zod.z.boolean().optional(),
1525
+ count: zod.z.number().int().nonnegative().optional(),
1526
+ locale: zod.z.enum(["en", "fr"]).default("en")
1527
+ });
1528
+ function validateBadgeProps(raw) {
1529
+ return BadgePropsSchema.parse(raw);
1530
+ }
1531
+ var variantClasses = {
1532
+ default: "bg-slate-100 text-slate-700 ring-1 ring-slate-200",
1533
+ primary: "bg-indigo-100 text-indigo-800 ring-1 ring-indigo-300",
1534
+ success: "bg-green-100 text-green-800 ring-1 ring-green-300",
1535
+ warning: "bg-amber-100 text-amber-800 ring-1 ring-amber-300",
1536
+ danger: "bg-red-100 text-red-800 ring-1 ring-red-300",
1537
+ info: "bg-blue-100 text-blue-800 ring-1 ring-blue-300",
1538
+ neutral: "bg-slate-100 text-slate-800 ring-1 ring-slate-300"
1539
+ };
1540
+ var dotClasses = {
1541
+ default: "bg-slate-400",
1542
+ primary: "bg-indigo-500",
1543
+ success: "bg-green-500",
1544
+ warning: "bg-amber-500",
1545
+ danger: "bg-red-500",
1546
+ info: "bg-blue-500",
1547
+ neutral: "bg-slate-400"
1548
+ };
1549
+ function BadgeInner({ label, variant, dot, count, locale }) {
1550
+ const ariaLabel = t("Badge.aria.label", locale);
1551
+ const displayCount = count !== void 0 ? ` (${count})` : "";
1552
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1553
+ "span",
1554
+ {
1555
+ "aria-label": `${ariaLabel}: ${label}${displayCount}`,
1556
+ lang: locale,
1557
+ className: `inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses[variant]}`,
1558
+ children: [
1559
+ dot && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: `h-1.5 w-1.5 rounded-full ${dotClasses[variant]}` }),
1560
+ label,
1561
+ count !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "font-semibold tabular-nums", children: count })
1562
+ ]
1563
+ }
1564
+ );
1565
+ }
1566
+ function Badge(raw) {
1567
+ try {
1568
+ const props = validateBadgeProps(raw);
1569
+ return /* @__PURE__ */ jsxRuntime.jsx(BadgeInner, { ...props });
1570
+ } catch {
1571
+ const locale = raw.locale === "fr" ? "fr" : "en";
1572
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("Badge.error.invalidProps", locale) });
1573
+ }
1574
+ }
1575
+
1576
+ // src/components/shared/decimal.ts
1577
+ var DECIMAL2 = /^(-)?(\d+)(?:\.(\d+))?$/;
1578
+ function sumDecimalStrings(values) {
1579
+ let scale = 0;
1580
+ const parsed = [];
1581
+ for (const v of values) {
1582
+ const m = DECIMAL2.exec(v);
1583
+ if (!m) return void 0;
1584
+ const frac2 = m[3] ?? "";
1585
+ scale = Math.max(scale, frac2.length);
1586
+ parsed.push({ neg: m[1] === "-", int: m[2] ?? "0", frac: frac2 });
1587
+ }
1588
+ let total = 0n;
1589
+ for (const p of parsed) {
1590
+ const n = BigInt(p.int + p.frac.padEnd(scale, "0"));
1591
+ total += p.neg ? -n : n;
1592
+ }
1593
+ const neg = total < 0n;
1594
+ const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
1595
+ const int = digits.slice(0, digits.length - scale);
1596
+ const frac = digits.slice(digits.length - scale).replace(/0+$/, "");
1597
+ return `${neg ? "-" : ""}${int}${frac ? `.${frac}` : ""}`;
1598
+ }
1599
+ function formatDecimalString(value, locale, currency) {
1600
+ if (!DECIMAL2.test(value)) return value;
1601
+ try {
1602
+ const nf = new Intl.NumberFormat(
1603
+ locale,
1604
+ currency ? { style: "currency", currency } : { maximumFractionDigits: 20 }
1605
+ );
1606
+ return nf.format(value);
1607
+ } catch {
1608
+ return value;
1609
+ }
1610
+ }
1611
+ function truncateAddress(address, head = 6, tail = 4) {
1612
+ return address.length <= head + tail + 1 ? address : `${address.slice(0, head)}\u2026${address.slice(address.length - tail)}`;
1613
+ }
1614
+ var BalanceCardPropsSchema = zod.z.object({
1615
+ token: zod.z.string().default(""),
1616
+ balance: zod.z.string().default(""),
1617
+ address: zod.z.string().default(""),
1618
+ tokenAddress: zod.z.string().optional(),
1619
+ decimals: zod.z.number().int().nonnegative().optional(),
1620
+ network: zod.z.string().optional(),
1621
+ testnet: zod.z.boolean().default(false),
1622
+ /** Decimal string in `currency`. */
1623
+ usdValue: zod.z.string().optional(),
1624
+ currency: zod.z.string().default("USD"),
1625
+ loading: zod.z.boolean().default(false),
1626
+ locale: zod.z.enum(["en", "fr"]).default("en")
1627
+ });
1628
+ function validateBalanceCardProps(raw) {
1629
+ return BalanceCardPropsSchema.parse(raw);
1630
+ }
1631
+ function BalanceCard(raw) {
1632
+ const parsed = BalanceCardPropsSchema.safeParse(raw);
1633
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1634
+ const [copied, setCopied] = react.useState(false);
1635
+ if (!parsed.success) {
1636
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("BalanceCard.error.invalidProps", locale) });
1637
+ }
1638
+ const p = parsed.data;
1639
+ if (p.loading) {
1640
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("BalanceCard.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rect", height: 120, locale: p.locale }) });
1641
+ }
1642
+ if (p.token === "" && p.balance === "") {
1643
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("BalanceCard.empty.message", p.locale), locale: p.locale });
1644
+ }
1645
+ const canCopy = typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
1646
+ const copy = async () => {
1647
+ try {
1648
+ await navigator.clipboard.writeText(p.address);
1649
+ setCopied(true);
1650
+ } catch {
1651
+ }
1652
+ };
1653
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1654
+ "section",
1655
+ {
1656
+ "aria-label": `${t("BalanceCard.title", p.locale)}: ${p.token}`,
1657
+ lang: p.locale,
1658
+ className: "flex flex-col gap-2",
1659
+ children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsx(
1661
+ StatCard,
1662
+ {
1663
+ label: t("BalanceCard.title", p.locale),
1664
+ value: p.balance,
1665
+ unit: p.token,
1666
+ locale: p.locale
1667
+ }
1668
+ ),
1669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1670
+ p.network ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: p.network, variant: "info", locale: p.locale }) : null,
1671
+ p.testnet ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: t("BalanceCard.testnet", p.locale), variant: "warning", locale: p.locale }) : null
1672
+ ] }),
1673
+ p.usdValue ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-slate-600", children: [
1674
+ t("BalanceCard.value", p.locale),
1675
+ ":",
1676
+ " ",
1677
+ formatDecimalString(p.usdValue, p.locale, p.currency)
1678
+ ] }) : null,
1679
+ p.address ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-slate-600", children: [
1680
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", title: p.address, children: truncateAddress(p.address) }),
1681
+ canCopy ? /* @__PURE__ */ jsxRuntime.jsx(
1682
+ "button",
1683
+ {
1684
+ type: "button",
1685
+ onClick: copy,
1686
+ className: "rounded border border-slate-300 px-2 py-0.5 text-xs hover:bg-slate-50",
1687
+ children: t("BalanceCard.copy", p.locale)
1688
+ }
1689
+ ) : null,
1690
+ copied ? /* @__PURE__ */ jsxRuntime.jsx("output", { children: t("BalanceCard.copied", p.locale) }) : null
1691
+ ] }) : null
1692
+ ]
1693
+ }
1694
+ );
1695
+ }
1696
+
1697
+ // src/components/display/BalanceCard.markdown.ts
1698
+ function balanceCardToMarkdown(props, locale = "en") {
1699
+ const parsed = BalanceCardPropsSchema.safeParse(props);
1700
+ const title = mdHeading(t("BalanceCard.title", locale));
1701
+ if (!parsed.success || parsed.data.token === "" && parsed.data.balance === "") {
1702
+ return `${title}
1703
+
1704
+ ${t("BalanceCard.empty.message", locale)}
1705
+ `;
1706
+ }
1707
+ const p = parsed.data;
1708
+ const where = [p.network, p.testnet ? t("BalanceCard.testnet", locale) : ""].filter(Boolean);
1709
+ const lines = [
1710
+ `- ${mdEscape(p.token)}: ${mdEscape(p.balance)}${where.length ? ` (${mdEscape(where.join(", "))})` : ""}`
1711
+ ];
1712
+ if (p.usdValue) {
1713
+ lines.push(
1714
+ `- ${t("BalanceCard.value", locale)}: ${mdEscape(formatDecimalString(p.usdValue, locale, p.currency))}`
1715
+ );
1716
+ }
1717
+ if (p.address) lines.push(`- ${t("BalanceCard.address", locale)}: ${mdCode(p.address)}`);
1718
+ if (p.tokenAddress)
1719
+ lines.push(`- ${t("BalanceCard.contract", locale)}: ${mdCode(p.tokenAddress)}`);
1720
+ return `${title}
1721
+
1722
+ ${lines.join("\n")}
1723
+ `;
1724
+ }
1725
+ var VirtualListPropsSchema = zod.z.object({
1726
+ /** Array of items to render — required, can be empty */
1727
+ items: zod.z.array(zod.z.unknown()),
1728
+ /** Fixed row height in px — mutually exclusive with estimateSize */
1729
+ itemHeight: zod.z.number().positive().optional(),
1730
+ /** Variable row height estimator — mutually exclusive with itemHeight */
1731
+ estimateSize: zod.z.function().args(zod.z.number()).returns(zod.z.number()).optional(),
1732
+ /** Row renderer — required (runtime function, excluded from strict Zod parse but validated for presence) */
1733
+ renderItem: zod.z.function().args(zod.z.unknown(), zod.z.number()).returns(zod.z.unknown()).optional(),
1734
+ /** Rows to render outside visible area (default: 5) */
1735
+ overscan: zod.z.number().int().min(0).optional(),
1736
+ /** CSS class applied to the scroll container */
1737
+ className: zod.z.string().optional(),
1738
+ /** Locale for built-in empty state + error messages */
1739
+ locale: zod.z.enum(["en", "fr"]).optional(),
1740
+ /** a11y: when provided, rows become keyboard-activable (role=button + tabIndex=0 + Enter/Space) */
1741
+ onRowClick: zod.z.function().args(zod.z.unknown(), zod.z.number()).returns(zod.z.void()).optional()
1742
+ });
1743
+ function validateVirtualListProps(raw) {
1744
+ return VirtualListPropsSchema.parse(raw);
1745
+ }
1746
+ var DEFAULT_OVERSCAN = 5;
1747
+ var DEFAULT_ITEM_HEIGHT = 40;
1748
+ function VirtualList(props) {
1749
+ try {
1750
+ validateVirtualListProps({
1751
+ items: props.items,
1752
+ itemHeight: props.itemHeight,
1753
+ overscan: props.overscan,
1754
+ className: props.className,
1755
+ locale: props.locale
1756
+ });
1757
+ } catch {
1758
+ const locale2 = props.locale === "fr" ? "fr" : "en";
1759
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("VirtualList.error.invalidProps", locale2) });
1760
+ }
1761
+ const locale = props.locale === "fr" ? "fr" : "en";
1762
+ if (props.items.length === 0) {
1763
+ return /* @__PURE__ */ jsxRuntime.jsx("output", { "aria-live": "polite", children: t("VirtualList.empty.message", locale) });
1764
+ }
1765
+ return /* @__PURE__ */ jsxRuntime.jsx(
1766
+ VirtualListInner,
1767
+ {
1768
+ items: props.items,
1769
+ itemHeight: props.itemHeight,
1770
+ estimateSize: props.estimateSize,
1771
+ renderItem: props.renderItem,
1772
+ onRowClick: props.onRowClick,
1773
+ overscan: props.overscan ?? DEFAULT_OVERSCAN,
1774
+ className: props.className,
1775
+ locale
1776
+ }
1777
+ );
1778
+ }
1779
+ function VirtualListInner({
1780
+ items,
1781
+ itemHeight,
1782
+ estimateSize,
1783
+ renderItem,
1784
+ onRowClick,
1785
+ overscan,
1786
+ className
1787
+ }) {
1788
+ const scrollRef = react.useRef(null);
1789
+ const resolvedEstimateSize = react.useCallback(
1790
+ (index) => {
1791
+ if (estimateSize) return estimateSize(index);
1792
+ return itemHeight ?? DEFAULT_ITEM_HEIGHT;
1793
+ },
1794
+ [estimateSize, itemHeight]
1795
+ );
1796
+ const rowVirtualizer = reactVirtual.useVirtualizer({
1797
+ count: items.length,
1798
+ /* v8 ignore next 1 */
1799
+ getScrollElement: () => scrollRef.current,
1800
+ estimateSize: resolvedEstimateSize,
1801
+ overscan
1802
+ });
1803
+ const virtualItems = rowVirtualizer.getVirtualItems();
1804
+ const totalSize = rowVirtualizer.getTotalSize();
1805
+ return /* @__PURE__ */ jsxRuntime.jsx(
1806
+ "div",
1807
+ {
1808
+ "data-virtual": "true",
1809
+ ref: scrollRef,
1810
+ className,
1811
+ style: { height: 400, overflow: "auto", position: "relative" },
1812
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: `${totalSize}px`, position: "relative" }, children: virtualItems.map((virtualRow) => {
1813
+ const item = items[virtualRow.index];
1814
+ const index = virtualRow.index;
1815
+ const rowProps = onRowClick ? {
1816
+ role: "button",
1817
+ tabIndex: 0,
1818
+ "data-list-row": true,
1819
+ style: {
1820
+ position: "absolute",
1821
+ top: 0,
1822
+ left: 0,
1823
+ width: "100%",
1824
+ transform: `translateY(${virtualRow.start}px)`,
1825
+ cursor: "pointer"
1826
+ },
1827
+ onClick: () => onRowClick(item, index),
1828
+ onKeyDown: (e) => {
1829
+ if (e.key === "Enter" || e.key === " ") {
1830
+ e.preventDefault();
1831
+ onRowClick(item, index);
1832
+ }
1833
+ }
1834
+ } : {
1835
+ "data-list-row": true,
1836
+ style: {
1837
+ position: "absolute",
1838
+ top: 0,
1839
+ left: 0,
1840
+ width: "100%",
1841
+ transform: `translateY(${virtualRow.start}px)`
1842
+ }
1843
+ };
1844
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...rowProps, children: renderItem(item, index) }, virtualRow.key);
1845
+ }) })
1846
+ }
1847
+ );
1848
+ }
1849
+
1850
+ // src/components/shared/url.ts
1851
+ function normalizeHttpUrl(value) {
1852
+ if (typeof value !== "string") return void 0;
1853
+ try {
1854
+ const u = new URL(value);
1855
+ return u.protocol === "https:" || u.protocol === "http:" ? u.href : void 0;
1856
+ } catch {
1857
+ return void 0;
1858
+ }
1859
+ }
1860
+ var FeedMessageSchema = zod.z.object({
1861
+ id: zod.z.string().optional(),
1862
+ sender: zod.z.string().min(1),
1863
+ channel: zod.z.string().optional(),
1864
+ /** Plain text. Rendered as text, never as HTML. */
1865
+ content: zod.z.string(),
1866
+ /** ISO 8601. */
1867
+ timestamp: zod.z.string(),
1868
+ attachmentUrl: zod.z.string().optional(),
1869
+ attachmentLabel: zod.z.string().optional(),
1870
+ unread: zod.z.boolean().optional()
1871
+ });
1872
+ var MessageFeedPropsSchema = zod.z.object({
1873
+ messages: zod.z.array(FeedMessageSchema).default([]),
1874
+ maxItems: zod.z.number().int().positive().optional(),
1875
+ /** IANA timezone for timestamps (e.g. `hostContext.timeZone`). */
1876
+ timeZone: zod.z.string().optional(),
1877
+ /** Above this many shown messages the feed is virtualised through VirtualList. */
1878
+ virtualizeThreshold: zod.z.number().int().positive().default(50),
1879
+ loading: zod.z.boolean().default(false),
1880
+ locale: zod.z.enum(["en", "fr"]).default("en")
1881
+ });
1882
+ function validateMessageFeedProps(raw) {
1883
+ return MessageFeedPropsSchema.parse(raw);
1884
+ }
1885
+ function latestMessages(messages, maxItems) {
1886
+ const shown = maxItems === void 0 ? [...messages] : messages.slice(-maxItems);
1887
+ return { shown, total: messages.length };
1888
+ }
1889
+ function Attachment({
1890
+ m,
1891
+ locale,
1892
+ onOpenLink
1893
+ }) {
1894
+ if (!m.attachmentUrl) return null;
1895
+ const label = m.attachmentLabel ?? t("MessageFeed.attachment", locale);
1896
+ const url = normalizeHttpUrl(m.attachmentUrl);
1897
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: label });
1898
+ return onOpenLink ? /* @__PURE__ */ jsxRuntime.jsx(
1899
+ "button",
1900
+ {
1901
+ type: "button",
1902
+ onClick: () => onOpenLink(url),
1903
+ className: "text-left text-sm text-blue-700 underline",
1904
+ children: label
1905
+ }
1906
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1907
+ "a",
1908
+ {
1909
+ href: url,
1910
+ rel: "noopener noreferrer",
1911
+ target: "_blank",
1912
+ className: "text-sm text-blue-700 underline",
1913
+ children: label
1914
+ }
1915
+ );
1916
+ }
1917
+ function Message({
1918
+ m,
1919
+ locale,
1920
+ timeZone,
1921
+ onOpenLink
1922
+ }) {
1923
+ return /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "flex flex-col gap-1 border-b border-slate-100 py-2", children: [
1924
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
1925
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: m.sender }),
1926
+ m.channel ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: m.channel, variant: "neutral", locale }) : null,
1927
+ /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: m.timestamp, className: "text-xs text-slate-500", children: formatTimestamp(m.timestamp, locale, timeZone) }),
1928
+ m.unread ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-blue-700", children: t("MessageFeed.unread", locale) }) : null
1929
+ ] }),
1930
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 whitespace-pre-wrap text-sm text-slate-800", children: m.content }),
1931
+ /* @__PURE__ */ jsxRuntime.jsx(Attachment, { m, locale, onOpenLink })
1932
+ ] });
637
1933
  }
638
-
639
- // src/components/display/Skeleton.logic.ts
640
- function getAnimationClass(animation) {
641
- switch (animation) {
642
- case "pulse":
643
- return "animate-pulse";
644
- case "wave":
645
- return "skeleton-wave";
646
- case "none":
647
- return "";
1934
+ function MessageFeed(raw = {}) {
1935
+ const parsed = MessageFeedPropsSchema.safeParse(raw);
1936
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1937
+ if (!parsed.success) {
1938
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("MessageFeed.error.invalidProps", locale) });
1939
+ }
1940
+ const p = parsed.data;
1941
+ if (p.loading) {
1942
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("MessageFeed.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "text", count: 5, locale: p.locale }) });
648
1943
  }
1944
+ if (p.messages.length === 0) {
1945
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("MessageFeed.empty.message", p.locale), locale: p.locale });
1946
+ }
1947
+ const { shown, total } = latestMessages(p.messages, p.maxItems);
1948
+ const virtual = shown.length > p.virtualizeThreshold;
1949
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": t("MessageFeed.title", p.locale), lang: p.locale, children: [
1950
+ shown.length < total ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 pb-1 text-xs text-slate-500", children: t("MessageFeed.showing", p.locale).replace("{n}", String(shown.length)).replace("{total}", String(total)) }) : null,
1951
+ virtual ? /* @__PURE__ */ jsxRuntime.jsx(
1952
+ VirtualList,
1953
+ {
1954
+ items: shown,
1955
+ itemHeight: 96,
1956
+ locale: p.locale,
1957
+ renderItem: (m) => /* @__PURE__ */ jsxRuntime.jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink })
1958
+ }
1959
+ ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "m-0 list-none p-0", children: shown.map((m, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink }) }, m.id ?? `msg-${i}`)) })
1960
+ ] });
649
1961
  }
650
- function getVariantClass(variant) {
651
- switch (variant) {
652
- case "rect":
653
- return "rounded-md";
654
- case "circle":
655
- return "rounded-full";
656
- case "text":
657
- return "rounded";
1962
+
1963
+ // src/components/display/MessageFeed.markdown.ts
1964
+ function messageFeedToMarkdown(props, locale = "en") {
1965
+ const parsed = MessageFeedPropsSchema.safeParse(props);
1966
+ const title = mdHeading(t("MessageFeed.title", locale));
1967
+ if (!parsed.success || parsed.data.messages.length === 0) {
1968
+ return `${title}
1969
+
1970
+ ${t("MessageFeed.empty.message", locale)}
1971
+ `;
658
1972
  }
1973
+ const { messages, maxItems, timeZone } = parsed.data;
1974
+ const { shown, total } = latestMessages(messages, maxItems);
1975
+ const lines = shown.map((m) => {
1976
+ const when = [formatTimestamp(m.timestamp, locale, timeZone), m.channel ?? ""].filter(Boolean).join(", ");
1977
+ const href = normalizeHttpUrl(m.attachmentUrl);
1978
+ const label = mdEscape(m.attachmentLabel ?? t("MessageFeed.attachment", locale));
1979
+ const attachment = m.attachmentUrl ? href ? ` [${label}](${mdUrl(href)})` : ` ${label}` : "";
1980
+ return `> **${mdEscape(m.sender)}** (${mdEscape(when)}): ${mdEscape(m.content)}${attachment}`;
1981
+ });
1982
+ const cut = shown.length < total ? `${t("MessageFeed.showing", locale).replace("{n}", String(shown.length)).replace("{total}", String(total))}
1983
+
1984
+ ` : "";
1985
+ return `${title}
1986
+
1987
+ ${cut}${lines.join("\n>\n")}
1988
+ `;
659
1989
  }
660
- var SKELETON_BG_CLASS = "bg-gray-200 dark:bg-gray-700";
661
- function textLineKey(index) {
662
- return `skeleton-line-${index}`;
1990
+
1991
+ // src/components/forms/RadioGroup.logic.ts
1992
+ function getOptionLabelClasses(appearance, isSelected) {
1993
+ const base = "mosaic-forms-radiogroup-option-label inline-flex items-center px-3 py-1 text-sm";
1994
+ const shape = "rounded-full border" ;
1995
+ const tone = isSelected ? "bg-slate-900 text-white border-slate-900" : "bg-white text-slate-700 border-slate-300 hover:bg-slate-50";
1996
+ return [base, shape, tone].filter(Boolean).join(" ");
663
1997
  }
664
- function lastLineWidth(index, total, baseWidth) {
665
- if (total > 1 && index === total - 1) {
666
- return "75%";
667
- }
668
- return baseWidth;
1998
+
1999
+ // src/components/display/PipelineBoard.logic.ts
2000
+ function stageSummary(deals) {
2001
+ const amounts = deals.flatMap((d) => d.amount === void 0 ? [] : [d.amount]);
2002
+ return { count: deals.length, total: sumDecimalStrings(amounts) };
669
2003
  }
670
- var SkeletonPropsSchema = zod.z.object({
671
- variant: zod.z.enum(["rect", "circle", "text"]).default("rect"),
672
- width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
673
- height: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
674
- count: zod.z.number().int().min(1).max(20).default(1),
675
- animation: zod.z.enum(["pulse", "wave", "none"]).default("pulse"),
2004
+ var SUMMARY_STAGE_LIMIT = 5;
2005
+ var PipelineDealSchema = zod.z.object({
2006
+ id: zod.z.string().min(1),
2007
+ title: zod.z.string().min(1),
2008
+ /** Decimal STRING (never a float), in the board currency. */
2009
+ amount: zod.z.string().optional(),
2010
+ contact: zod.z.string().optional(),
2011
+ url: zod.z.string().optional()
2012
+ });
2013
+ var PipelineStageSchema = zod.z.object({
2014
+ id: zod.z.string().min(1),
2015
+ name: zod.z.string().min(1),
2016
+ deals: zod.z.array(PipelineDealSchema).default([])
2017
+ });
2018
+ var PipelineBoardPropsSchema = zod.z.object({
2019
+ stages: zod.z.array(PipelineStageSchema).default([]),
2020
+ /** ISO 4217 code used to format amounts. */
2021
+ currency: zod.z.string().default("EUR"),
2022
+ layout: zod.z.enum(["board", "summary"]).default("board"),
2023
+ loading: zod.z.boolean().default(false),
676
2024
  locale: zod.z.enum(["en", "fr"]).default("en")
677
2025
  });
678
- function validateSkeletonProps(raw) {
679
- return SkeletonPropsSchema.parse(raw);
2026
+ function validatePipelineBoardProps(raw) {
2027
+ return PipelineBoardPropsSchema.parse(raw);
680
2028
  }
681
- function resolveSkeletonDimensions(variant, width, height) {
682
- const toCss = (v, fallback) => {
683
- if (v === void 0) return fallback;
684
- if (typeof v === "number") return `${v}px`;
685
- return v;
686
- };
687
- if (variant === "circle") {
688
- const size = width !== void 0 ? toCss(width, "40px") : height !== void 0 ? toCss(height, "40px") : "40px";
689
- return { widthCss: size, heightCss: size };
2029
+ function dealsLabel(n, locale) {
2030
+ return n === 1 ? t("PipelineBoard.deals.one", locale) : t("PipelineBoard.deals.many", locale).replace("{n}", String(n));
2031
+ }
2032
+ function Totals({
2033
+ stage,
2034
+ currency,
2035
+ locale
2036
+ }) {
2037
+ const { count, total } = stageSummary(stage.deals);
2038
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col text-sm text-slate-600", children: [
2039
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: dealsLabel(count, locale) }),
2040
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: total === void 0 ? "\u2014" : formatDecimalString(total, locale, currency) })
2041
+ ] });
2042
+ }
2043
+ function PipelineBoard(raw = {}) {
2044
+ const parsed = PipelineBoardPropsSchema.safeParse(raw);
2045
+ const locale = raw?.locale === "fr" ? "fr" : "en";
2046
+ const [hidden, setHidden] = react.useState(/* @__PURE__ */ new Set());
2047
+ if (!parsed.success) {
2048
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("PipelineBoard.error.invalidProps", locale) });
690
2049
  }
691
- if (variant === "text") {
692
- return {
693
- widthCss: toCss(width, "100%"),
694
- heightCss: toCss(height, "16px")
695
- };
2050
+ const p = parsed.data;
2051
+ if (p.loading) {
2052
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("PipelineBoard.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rect", height: 160, locale: p.locale }) });
696
2053
  }
697
- return {
698
- widthCss: toCss(width, "100%"),
699
- heightCss: toCss(height, "100px")
700
- };
701
- }
702
- function SkeletonInner({ variant, width, height, count, animation, locale }) {
703
- const { widthCss, heightCss } = resolveSkeletonDimensions(variant, width, height);
704
- const animClass = getAnimationClass(animation);
705
- const variantClass = getVariantClass(variant);
706
- const ariaLabel = t("Skeleton.aria.label", locale);
707
- const baseClass = `${SKELETON_BG_CLASS} ${variantClass} ${animClass}`.trim();
708
- if (variant === "text") {
709
- return (
710
- // biome-ignore lint/a11y/useSemanticElements: role="status" is the correct ARIA pattern for loading indicators; <output> conveys form output semantics which is incorrect here
711
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-label": ariaLabel, "aria-busy": "true", className: "flex flex-col gap-2", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
712
- "span",
713
- {
714
- className: `block ${baseClass}`,
715
- style: {
716
- width: lastLineWidth(i, count, widthCss),
717
- height: heightCss
718
- },
719
- "aria-hidden": "true"
720
- },
721
- textLineKey(i)
722
- )) })
2054
+ if (p.stages.length === 0) {
2055
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("PipelineBoard.empty.message", p.locale), locale: p.locale });
2056
+ }
2057
+ if (p.layout === "summary") {
2058
+ const shown = p.stages.slice(0, SUMMARY_STAGE_LIMIT);
2059
+ const rest = p.stages.length - shown.length;
2060
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2061
+ "section",
2062
+ {
2063
+ "aria-label": t("PipelineBoard.title", p.locale),
2064
+ lang: p.locale,
2065
+ className: "flex flex-col gap-2",
2066
+ children: [
2067
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: shown.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-2", children: [
2068
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: s.name }),
2069
+ /* @__PURE__ */ jsxRuntime.jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
2070
+ ] }, s.id)) }),
2071
+ rest > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.more", p.locale).replace("{n}", String(rest)) }) : null,
2072
+ raw.onRequestFullscreen ? /* @__PURE__ */ jsxRuntime.jsx(
2073
+ "button",
2074
+ {
2075
+ type: "button",
2076
+ onClick: raw.onRequestFullscreen,
2077
+ className: "self-start rounded border border-slate-300 px-3 py-1 text-sm hover:bg-slate-50",
2078
+ children: t("PipelineBoard.summary.open", p.locale)
2079
+ }
2080
+ ) : null
2081
+ ]
2082
+ }
723
2083
  );
724
2084
  }
725
- return /* @__PURE__ */ jsxRuntime.jsx(
726
- "div",
2085
+ const visible = p.stages.filter((s) => !hidden.has(s.id));
2086
+ const toggle = (id) => {
2087
+ const next = new Set(hidden);
2088
+ if (next.has(id)) next.delete(id);
2089
+ else if (visible.length > 1) next.add(id);
2090
+ setHidden(next);
2091
+ };
2092
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2093
+ "section",
727
2094
  {
728
- role: "status",
729
- "aria-label": ariaLabel,
730
- "aria-busy": "true",
731
- className: `block ${baseClass}`,
732
- style: { width: widthCss, height: heightCss }
2095
+ "aria-label": t("PipelineBoard.title", p.locale),
2096
+ lang: p.locale,
2097
+ className: "flex flex-col gap-3",
2098
+ children: [
2099
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "m-0 flex min-w-0 flex-wrap gap-2 border-0 p-0", children: [
2100
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "sr-only", children: t("PipelineBoard.filter.label", p.locale) }),
2101
+ p.stages.map((s) => {
2102
+ const on = !hidden.has(s.id);
2103
+ return /* @__PURE__ */ jsxRuntime.jsx(
2104
+ "button",
2105
+ {
2106
+ type: "button",
2107
+ "aria-pressed": on,
2108
+ onClick: () => toggle(s.id),
2109
+ className: getOptionLabelClasses("chips", on),
2110
+ children: s.name
2111
+ },
2112
+ s.id
2113
+ );
2114
+ })
2115
+ ] }),
2116
+ /* @__PURE__ */ jsxRuntime.jsx(
2117
+ "div",
2118
+ {
2119
+ className: "flex flex-row gap-3 overflow-x-auto",
2120
+ style: {
2121
+ scrollPaddingLeft: "var(--mosaic-safe-area-left, 0px)",
2122
+ scrollPaddingRight: "var(--mosaic-safe-area-right, 0px)"
2123
+ },
2124
+ children: visible.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
2125
+ "section",
2126
+ {
2127
+ "aria-label": s.name,
2128
+ className: "flex w-64 shrink-0 flex-col gap-2 rounded-lg border border-slate-200 bg-slate-50 p-3",
2129
+ children: [
2130
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-start justify-between gap-2", children: [
2131
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: s.name }),
2132
+ /* @__PURE__ */ jsxRuntime.jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
2133
+ ] }),
2134
+ s.deals.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.stage.empty", p.locale) }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: s.deals.map((d) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "rounded border border-slate-200 bg-white p-2", children: [
2135
+ raw.onOpenLink && normalizeHttpUrl(d.url) ? /* @__PURE__ */ jsxRuntime.jsx(
2136
+ "button",
2137
+ {
2138
+ type: "button",
2139
+ onClick: () => raw.onOpenLink?.(normalizeHttpUrl(d.url)),
2140
+ className: "text-left font-medium text-blue-700 underline",
2141
+ children: d.title
2142
+ }
2143
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: d.title }),
2144
+ d.amount !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-sm tabular-nums text-slate-700", children: formatDecimalString(d.amount, p.locale, p.currency) }) : null,
2145
+ d.contact ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: d.contact, variant: "neutral", locale: p.locale }) : null
2146
+ ] }, d.id)) })
2147
+ ]
2148
+ },
2149
+ s.id
2150
+ ))
2151
+ }
2152
+ )
2153
+ ]
733
2154
  }
734
2155
  );
735
2156
  }
736
- function Skeleton(raw) {
737
- try {
738
- const props = validateSkeletonProps(raw);
739
- return /* @__PURE__ */ jsxRuntime.jsx(SkeletonInner, { ...props });
740
- } catch {
741
- const locale = raw.locale === "fr" ? "fr" : "en";
742
- return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "text-red-600 text-sm", children: t("Skeleton.error.invalidProps", locale) });
2157
+
2158
+ // src/components/display/PipelineBoard.markdown.ts
2159
+ function pipelineBoardToMarkdown(props, locale = "en") {
2160
+ const parsed = PipelineBoardPropsSchema.safeParse(props);
2161
+ const title = mdHeading(t("PipelineBoard.title", locale));
2162
+ if (!parsed.success || parsed.data.stages.length === 0) {
2163
+ return `${title}
2164
+
2165
+ ${t("PipelineBoard.empty.message", locale)}
2166
+ `;
743
2167
  }
2168
+ const { stages, currency } = parsed.data;
2169
+ const money = (v) => mdEscape(formatDecimalString(v, locale, currency));
2170
+ const table = mdTable(
2171
+ [
2172
+ t("PipelineBoard.col.stage", locale),
2173
+ t("PipelineBoard.col.deals", locale),
2174
+ t("PipelineBoard.total", locale)
2175
+ ],
2176
+ stages.map((s) => {
2177
+ const sum = stageSummary(s.deals);
2178
+ return [
2179
+ mdEscape(s.name),
2180
+ String(sum.count),
2181
+ sum.total === void 0 ? "-" : money(sum.total)
2182
+ ];
2183
+ })
2184
+ );
2185
+ const sections = stages.filter((s) => s.deals.length > 0).map((s) => {
2186
+ const lines = s.deals.map((d) => {
2187
+ const amount = d.amount === void 0 ? "" : ` (${money(d.amount)})`;
2188
+ const contact = d.contact ? ` - ${mdEscape(d.contact)}` : "";
2189
+ return `- ${mdEscape(d.title)}${amount}${contact}`;
2190
+ });
2191
+ return `${mdHeading(mdEscape(s.name), 2)}
2192
+
2193
+ ${lines.join("\n")}`;
2194
+ });
2195
+ return `${title}
2196
+
2197
+ ${table}
2198
+ ${sections.length ? `
2199
+ ${sections.join("\n\n")}
2200
+ ` : ""}`;
744
2201
  }
745
2202
  var MarkdownRendererPropsSchema = zod.z.object({
746
2203
  content: zod.z.string(),
@@ -792,6 +2249,11 @@ var TokenDisplayOnceModalPropsSchema = zod.z.object({
792
2249
  warningMessage: zod.z.string().min(1),
793
2250
  copyLabel: zod.z.string().min(1),
794
2251
  closeLabel: zod.z.string().min(1),
2252
+ /** Non-secret metadata, shown in the view AND carried by the A-1 fallback. */
2253
+ scope: zod.z.string().optional(),
2254
+ expiresAt: zod.z.string().optional(),
2255
+ /** Supplied by the issuer; NEVER derived from the secret. */
2256
+ fingerprint: zod.z.string().optional(),
795
2257
  locale: zod.z.enum(["en", "fr"]).default("en")
796
2258
  });
797
2259
  function validateTokenDisplayOnceModalProps(raw) {
@@ -803,6 +2265,9 @@ function TokenDisplayOnceModalInner({
803
2265
  warningMessage,
804
2266
  copyLabel,
805
2267
  closeLabel,
2268
+ scope,
2269
+ expiresAt,
2270
+ fingerprint,
806
2271
  locale,
807
2272
  onClose
808
2273
  }) {
@@ -865,6 +2330,29 @@ function TokenDisplayOnceModalInner({
865
2330
  children: warningMessage
866
2331
  }
867
2332
  ),
2333
+ scope || expiresAt || fingerprint ? /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "m-0 text-sm text-gray-700", children: [
2334
+ scope ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2335
+ /* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "inline", children: [
2336
+ t("TokenDisplayOnceModal.field.scope", locale),
2337
+ ": "
2338
+ ] }),
2339
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "m-0 inline", children: scope })
2340
+ ] }) : null,
2341
+ expiresAt ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2342
+ /* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "inline", children: [
2343
+ t("TokenDisplayOnceModal.field.expires", locale),
2344
+ ": "
2345
+ ] }),
2346
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "m-0 inline", children: expiresAt })
2347
+ ] }) : null,
2348
+ fingerprint ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2349
+ /* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "inline", children: [
2350
+ t("TokenDisplayOnceModal.field.fingerprint", locale),
2351
+ ": "
2352
+ ] }),
2353
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "m-0 inline font-mono", children: fingerprint })
2354
+ ] }) : null
2355
+ ] }) : null,
868
2356
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 bg-gray-50 border border-gray-200 rounded-md px-3 py-2", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "flex-1 text-sm font-mono text-gray-800 break-all select-all", children: secureToken }) }),
869
2357
  /* @__PURE__ */ jsxRuntime.jsx("output", { "aria-live": "polite", "aria-atomic": "true", className: "min-h-[1.25rem] block", children: copied && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-green-700 font-medium", children: t("TokenDisplayOnceModal.copied", locale) }) }),
870
2358
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 mt-2", children: [
@@ -922,6 +2410,202 @@ var AlertPropsSchema = zod.z.object({
922
2410
  function validateAlertProps(raw) {
923
2411
  return AlertPropsSchema.parse(raw);
924
2412
  }
2413
+ var AmountSchema = zod.z.object({
2414
+ /** Decimal STRING (never a float). */
2415
+ value: zod.z.string().min(1),
2416
+ symbol: zod.z.string().min(1)
2417
+ });
2418
+ var QuoteSchema = zod.z.object({
2419
+ sell: AmountSchema,
2420
+ buy: AmountSchema,
2421
+ minReceived: AmountSchema.optional(),
2422
+ route: zod.z.array(zod.z.string().min(1)).optional(),
2423
+ /** Percent as a decimal string, e.g. "0.42". */
2424
+ priceImpact: zod.z.string().optional()
2425
+ });
2426
+ var TransactionPreviewPropsSchema = zod.z.object({
2427
+ kind: zod.z.enum(["transfer", "swap"]).default("transfer"),
2428
+ network: zod.z.string().optional(),
2429
+ testnet: zod.z.boolean().default(false),
2430
+ from: zod.z.string().optional(),
2431
+ to: zod.z.string().optional(),
2432
+ amount: AmountSchema.optional(),
2433
+ fee: AmountSchema.optional(),
2434
+ quote: QuoteSchema.optional(),
2435
+ simulation: zod.z.object({
2436
+ status: zod.z.enum(["success", "failed", "unavailable"]),
2437
+ message: zod.z.string().optional()
2438
+ }).optional(),
2439
+ warnings: zod.z.array(zod.z.string()).default([]),
2440
+ /** ISO 8601 expiry of the quote. */
2441
+ expiresAt: zod.z.string().optional(),
2442
+ /** Wallet link-out. Only http(s) urls are ever rendered. */
2443
+ handoff: zod.z.object({ url: zod.z.string().min(1) }).optional(),
2444
+ timeZone: zod.z.string().optional(),
2445
+ loading: zod.z.boolean().default(false),
2446
+ locale: zod.z.enum(["en", "fr"]).default("en")
2447
+ });
2448
+ function validateTransactionPreviewProps(raw) {
2449
+ return TransactionPreviewPropsSchema.parse(raw);
2450
+ }
2451
+ function hasPreviewContent(p) {
2452
+ return Boolean(p.from || p.to || p.amount || p.quote || p.fee);
2453
+ }
2454
+ var amountText = (a) => `${a.value} ${a.symbol}`;
2455
+ function Row({ label, children }) {
2456
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5 py-1 sm:flex-row sm:gap-3", children: [
2457
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "w-36 shrink-0 text-sm text-slate-500", children: label }),
2458
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "m-0 min-w-0 break-all text-sm text-slate-900", children })
2459
+ ] });
2460
+ }
2461
+ function TransactionPreview(raw = {}) {
2462
+ const parsed = TransactionPreviewPropsSchema.safeParse(raw);
2463
+ const locale = raw?.locale === "fr" ? "fr" : "en";
2464
+ if (!parsed.success) {
2465
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TransactionPreview.error.invalidProps", locale) });
2466
+ }
2467
+ const p = parsed.data;
2468
+ if (p.loading) {
2469
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("TransactionPreview.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "text", count: 6, locale: p.locale }) });
2470
+ }
2471
+ if (!hasPreviewContent(p)) {
2472
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("TransactionPreview.empty.message", p.locale), locale: p.locale });
2473
+ }
2474
+ const highImpact = p.quote?.priceImpact !== void 0 && compareDecimalStrings(p.quote.priceImpact, "1") >= 0;
2475
+ const handoff = normalizeHttpUrl(p.handoff?.url);
2476
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2477
+ "section",
2478
+ {
2479
+ "aria-label": t("TransactionPreview.title", p.locale),
2480
+ lang: p.locale,
2481
+ className: "flex flex-col gap-3 rounded-lg border border-slate-200 bg-white p-4",
2482
+ children: [
2483
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-wrap items-center gap-2", children: [
2484
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: t(`TransactionPreview.kind.${p.kind}`, p.locale) }),
2485
+ p.testnet ? /* @__PURE__ */ jsxRuntime.jsx(
2486
+ Badge,
2487
+ {
2488
+ label: t("TransactionPreview.testnet", p.locale),
2489
+ variant: "warning",
2490
+ locale: p.locale
2491
+ }
2492
+ ) : null
2493
+ ] }),
2494
+ /* @__PURE__ */ jsxRuntime.jsx(
2495
+ "p",
2496
+ {
2497
+ role: "note",
2498
+ className: "m-0 rounded bg-amber-50 px-3 py-2 text-sm font-semibold text-amber-900",
2499
+ children: t("TransactionPreview.notice", p.locale)
2500
+ }
2501
+ ),
2502
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "m-0", children: [
2503
+ p.network ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.network", p.locale), children: p.network }) : null,
2504
+ p.from ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.from", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", children: p.from }) }) : null,
2505
+ p.to ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.to", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", children: p.to }) }) : null,
2506
+ p.amount ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.amount", p.locale), children: amountText(p.amount) }) : null,
2507
+ p.quote ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2508
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.sell", p.locale), children: amountText(p.quote.sell) }),
2509
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.buy", p.locale), children: amountText(p.quote.buy) }),
2510
+ p.quote.minReceived ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.minReceived", p.locale), children: amountText(p.quote.minReceived) }) : null,
2511
+ p.quote.route ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.route", p.locale), children: p.quote.route.join(" > ") }) : null,
2512
+ p.quote.priceImpact !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs(Row, { label: t("TransactionPreview.field.priceImpact", p.locale), children: [
2513
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: `${p.quote.priceImpact}%` }),
2514
+ highImpact ? /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "ml-2 text-red-700", children: t("TransactionPreview.priceImpact.high", p.locale) }) : null
2515
+ ] }) : null
2516
+ ] }) : null,
2517
+ p.fee ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.fee", p.locale), children: amountText(p.fee) }) : null,
2518
+ p.expiresAt ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("TransactionPreview.field.expires", p.locale), children: formatTimestamp(p.expiresAt, p.locale, p.timeZone) }) : null
2519
+ ] }),
2520
+ p.simulation ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm", children: [
2521
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: p.simulation.status === "failed" ? "text-red-700" : "text-slate-800", children: t(`TransactionPreview.simulation.${p.simulation.status}`, p.locale) }),
2522
+ p.simulation.message ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-slate-600", children: p.simulation.message }) : null
2523
+ ] }) : null,
2524
+ p.warnings.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm", children: [
2525
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-amber-800", children: t("TransactionPreview.warnings", p.locale) }),
2526
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 pl-5", children: p.warnings.map((w) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: w }, w)) })
2527
+ ] }) : null,
2528
+ handoff ? raw.onHandoff ? /* @__PURE__ */ jsxRuntime.jsx(
2529
+ "button",
2530
+ {
2531
+ type: "button",
2532
+ onClick: () => raw.onHandoff?.(handoff),
2533
+ className: "self-start rounded border border-slate-300 px-3 py-1 text-sm hover:bg-slate-50",
2534
+ children: t("TransactionPreview.handoff", p.locale)
2535
+ }
2536
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2537
+ "a",
2538
+ {
2539
+ href: handoff,
2540
+ rel: "noopener noreferrer",
2541
+ target: "_blank",
2542
+ className: "self-start text-sm text-blue-700 underline",
2543
+ children: t("TransactionPreview.handoff", p.locale)
2544
+ }
2545
+ ) : null
2546
+ ]
2547
+ }
2548
+ );
2549
+ }
2550
+
2551
+ // src/components/confirmation/TransactionPreview.markdown.ts
2552
+ var amt = (a) => `${a.value} ${a.symbol}`;
2553
+ function transactionPreviewToMarkdown(props, locale = "en") {
2554
+ const title = mdHeading(t("TransactionPreview.title", locale));
2555
+ const notice = `**${t("TransactionPreview.notice", locale)}**`;
2556
+ const parsed = TransactionPreviewPropsSchema.safeParse(props);
2557
+ if (!parsed.success || !hasPreviewContent(parsed.data)) {
2558
+ return `${title}
2559
+
2560
+ ${notice}
2561
+
2562
+ ${t("TransactionPreview.empty.message", locale)}
2563
+ `;
2564
+ }
2565
+ const p = parsed.data;
2566
+ const rows = [];
2567
+ const add = (key, value) => {
2568
+ if (value) rows.push([t(key, locale), mdEscape(value)]);
2569
+ };
2570
+ add(
2571
+ "TransactionPreview.field.network",
2572
+ [p.network, p.testnet ? t("TransactionPreview.testnet", locale) : ""].filter(Boolean).join(", ")
2573
+ );
2574
+ add("TransactionPreview.field.from", p.from);
2575
+ add("TransactionPreview.field.to", p.to);
2576
+ if (p.amount) add("TransactionPreview.field.amount", amt(p.amount));
2577
+ if (p.quote) {
2578
+ add("TransactionPreview.field.sell", amt(p.quote.sell));
2579
+ add("TransactionPreview.field.buy", amt(p.quote.buy));
2580
+ if (p.quote.minReceived) add("TransactionPreview.field.minReceived", amt(p.quote.minReceived));
2581
+ if (p.quote.route) add("TransactionPreview.field.route", p.quote.route.join(" \u2192 "));
2582
+ if (p.quote.priceImpact) add("TransactionPreview.field.priceImpact", `${p.quote.priceImpact}%`);
2583
+ }
2584
+ if (p.fee) add("TransactionPreview.field.fee", amt(p.fee));
2585
+ if (p.expiresAt) {
2586
+ add("TransactionPreview.field.expires", formatTimestamp(p.expiresAt, locale, p.timeZone));
2587
+ }
2588
+ const sections = [
2589
+ `${title}
2590
+
2591
+ ${notice}`,
2592
+ mdTable([t(`TransactionPreview.kind.${p.kind}`, locale), ""], rows)
2593
+ ];
2594
+ if (p.simulation) {
2595
+ const msg = p.simulation.message ? ` - ${mdEscape(p.simulation.message)}` : "";
2596
+ sections.push(`${t(`TransactionPreview.simulation.${p.simulation.status}`, locale)}${msg}`);
2597
+ }
2598
+ if (p.warnings.length > 0) {
2599
+ sections.push(
2600
+ `${t("TransactionPreview.warnings", locale)}:
2601
+ ${p.warnings.map((w) => `- ${mdEscape(w)}`).join("\n")}`
2602
+ );
2603
+ }
2604
+ const handoff = normalizeHttpUrl(p.handoff?.url);
2605
+ if (handoff) sections.push(`${t("TransactionPreview.handoff", locale)}: <${mdUrl(handoff)}>`);
2606
+ return `${sections.join("\n\n")}
2607
+ `;
2608
+ }
925
2609
  var StatusBadgePropsSchema = zod.z.object({
926
2610
  status: zod.z.string(),
927
2611
  variant: zod.z.enum(["success", "warning", "danger", "info", "neutral"]).default("neutral"),
@@ -931,7 +2615,7 @@ var StatusBadgePropsSchema = zod.z.object({
931
2615
  function validateProps3(raw) {
932
2616
  return StatusBadgePropsSchema.parse(raw);
933
2617
  }
934
- var variantClasses = {
2618
+ var variantClasses2 = {
935
2619
  success: "bg-green-100 text-green-800 ring-1 ring-green-300",
936
2620
  warning: "bg-amber-100 text-amber-800 ring-1 ring-amber-300",
937
2621
  danger: "bg-red-100 text-red-800 ring-1 ring-red-300",
@@ -945,7 +2629,7 @@ function StatusBadgeInner({ status, variant, label, locale }) {
945
2629
  {
946
2630
  "aria-label": ariaLabel,
947
2631
  lang: locale,
948
- className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses[variant]}`,
2632
+ className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses2[variant]}`,
949
2633
  children: label ?? status
950
2634
  }
951
2635
  );
@@ -984,6 +2668,122 @@ function createPostMessageObservable() {
984
2668
  }
985
2669
  return subject.asObservable();
986
2670
  }
2671
+ var SIZE_CHANGED_METHOD = "ui/notifications/size-changed";
2672
+ function postSizeChanged(size, target = window.parent) {
2673
+ target.postMessage(
2674
+ {
2675
+ jsonrpc: "2.0",
2676
+ method: SIZE_CHANGED_METHOD,
2677
+ params: { width: size.width, height: size.height }
2678
+ },
2679
+ "*"
2680
+ );
2681
+ }
2682
+ function startSizeReporting(options = {}) {
2683
+ const RO = options.ResizeObserverImpl === void 0 ? typeof ResizeObserver === "undefined" ? null : ResizeObserver : options.ResizeObserverImpl;
2684
+ if (RO === null) throw new Error("startSizeReporting: ResizeObserver is unavailable");
2685
+ const element = options.element ?? document.documentElement;
2686
+ const send = options.send ?? ((s) => postSizeChanged(s));
2687
+ let last;
2688
+ const report = () => {
2689
+ const rect = element.getBoundingClientRect();
2690
+ const size = {
2691
+ width: Math.ceil(rect.width),
2692
+ height: Math.ceil(Math.max(rect.height, element.scrollHeight ?? 0))
2693
+ };
2694
+ if (last && last.width === size.width && last.height === size.height) return;
2695
+ last = size;
2696
+ send(size);
2697
+ };
2698
+ const observer = new RO(report);
2699
+ observer.observe(element);
2700
+ report();
2701
+ return () => observer.disconnect();
2702
+ }
2703
+
2704
+ // src/components/artifacts/MarkdownRenderer.markdown.ts
2705
+ function markdownRendererToMarkdown(props, locale = "en") {
2706
+ const p = MarkdownRendererPropsSchema.parse({ locale, ...props });
2707
+ return `${mdHeading(t("MarkdownRenderer.title", locale))}
2708
+
2709
+ ${p.content.slice(0, p.maxLength)}
2710
+ `;
2711
+ }
2712
+
2713
+ // src/components/confirmation/TokenDisplayOnceModal.markdown.ts
2714
+ function tokenDisplayOnceModalToMarkdown(props, locale = "en") {
2715
+ const p = TokenDisplayOnceModalPropsSchema.omit({ token: true }).parse(props);
2716
+ const rows = [
2717
+ p.scope ? `- ${t("TokenDisplayOnceModal.field.scope", locale)}: ${mdEscape(p.scope)}` : "",
2718
+ p.expiresAt ? `- ${t("TokenDisplayOnceModal.field.expires", locale)}: ${mdEscape(p.expiresAt)}` : "",
2719
+ p.fingerprint ? `- ${t("TokenDisplayOnceModal.field.fingerprint", locale)}: ${mdEscape(p.fingerprint)}` : ""
2720
+ ].filter(Boolean);
2721
+ return `${mdHeading(t("TokenDisplayOnceModal.title", locale))}
2722
+
2723
+ **${mdEscape(p.title)}**
2724
+
2725
+ ${rows.length ? `${rows.join("\n")}
2726
+
2727
+ ` : ""}${t("TokenDisplayOnceModal.notice.once", locale)}
2728
+ `;
2729
+ }
2730
+
2731
+ // src/components/display/TableView.markdown.ts
2732
+ var MARKDOWN_MAX_ROWS = 50;
2733
+ var MASK = "\u2022\u2022\u2022\u2022";
2734
+ function tableViewToMarkdown(props, locale = "en") {
2735
+ const p = TableViewPropsSchema.parse(props);
2736
+ const shown = p.rows.slice(0, MARKDOWN_MAX_ROWS);
2737
+ const table = mdTable(
2738
+ p.columns.map((c) => mdEscape(c.header)),
2739
+ shown.map((r) => p.columns.map((c) => c.sensitive ? MASK : mdEscape(String(r[c.key] ?? ""))))
2740
+ );
2741
+ const rest = p.rows.length - shown.length;
2742
+ const more = rest > 0 ? `
2743
+ ${t("TableView.markdown.more", locale).replace("{n}", String(rest))}
2744
+ ` : "";
2745
+ const empty = p.rows.length === 0 ? `
2746
+ ${t("TableView.empty.message", locale)}
2747
+ ` : "";
2748
+ return `${mdHeading(t("TableView.title", locale))}
2749
+
2750
+ ${table}
2751
+ ${more}${empty}`;
2752
+ }
2753
+
2754
+ // src/components/input/ConfirmDialog.markdown.ts
2755
+ function confirmDialogToMarkdown(props, locale = "en") {
2756
+ const p = ConfirmDialogPropsSchema.parse(props);
2757
+ return `${mdHeading(t("ConfirmDialog.title", locale))}
2758
+
2759
+ **${mdEscape(p.title)}**
2760
+
2761
+ ${mdEscape(p.message)}
2762
+
2763
+ ${t("ConfirmDialog.options", locale)}: ${mdEscape(p.confirmLabel)} / ${mdEscape(p.cancelLabel)}
2764
+ `;
2765
+ }
2766
+
2767
+ // src/components/media/StatusBadge.markdown.ts
2768
+ function statusBadgeToMarkdown(props, locale = "en") {
2769
+ const p = StatusBadgePropsSchema.parse({ locale, ...props });
2770
+ return `${mdHeading(t("StatusBadge.title", locale))}
2771
+
2772
+ - ${mdEscape(p.label ?? p.status)} (${p.variant})
2773
+ `;
2774
+ }
2775
+
2776
+ // src/components/progress/ProgressBar.markdown.ts
2777
+ function progressBarToMarkdown(props, locale = "en") {
2778
+ const p = ProgressBarPropsSchema.parse(props);
2779
+ const variant = p.colorVariant === "default" ? "" : ` (${p.colorVariant})`;
2780
+ return `${mdHeading(t("ProgressBar.title", locale))}
2781
+
2782
+ - ${mdEscape(p.label)}: ${p.value}%${variant}
2783
+ `;
2784
+ }
2785
+
2786
+ // src/server/create-mosaic-resource.ts
987
2787
  var MCP_UI_CAPABILITY_KEY = "io.modelcontextprotocol/ui";
988
2788
  var MOSAIC_MIME_TYPE = "text/html;profile=mcp-app";
989
2789
  var SCHEMA_BY_NAME = {
@@ -992,42 +2792,50 @@ var SCHEMA_BY_NAME = {
992
2792
  TableView: TableViewPropsSchema,
993
2793
  MarkdownRenderer: MarkdownRendererPropsSchema,
994
2794
  TokenDisplayOnceModal: TokenDisplayOnceModalPropsSchema,
995
- StatusBadge: StatusBadgePropsSchema
2795
+ StatusBadge: StatusBadgePropsSchema,
2796
+ StatCard: StatCardPropsSchema,
2797
+ BalanceCard: BalanceCardPropsSchema,
2798
+ Timeline: TimelinePropsSchema,
2799
+ MessageFeed: MessageFeedPropsSchema,
2800
+ PipelineBoard: PipelineBoardPropsSchema,
2801
+ TransactionPreview: TransactionPreviewPropsSchema
996
2802
  };
997
- var COMPONENT_KEYS = Object.keys(SCHEMA_BY_NAME);
998
- var I18N_TITLE = {
999
- ProgressBar: { en: "Progress", fr: "Progression" },
1000
- ConfirmDialog: { en: "Confirmation", fr: "Confirmation" },
1001
- TableView: { en: "Data table", fr: "Tableau de donn\xE9es" },
1002
- MarkdownRenderer: { en: "Markdown content", fr: "Contenu Markdown" },
1003
- TokenDisplayOnceModal: { en: "Token (shown once)", fr: "Jeton (affich\xE9 une fois)" },
1004
- StatusBadge: { en: "Status", fr: "Statut" }
2803
+ var MARKDOWN_BY_NAME = {
2804
+ ProgressBar: progressBarToMarkdown,
2805
+ ConfirmDialog: confirmDialogToMarkdown,
2806
+ TableView: tableViewToMarkdown,
2807
+ MarkdownRenderer: markdownRendererToMarkdown,
2808
+ TokenDisplayOnceModal: tokenDisplayOnceModalToMarkdown,
2809
+ StatusBadge: statusBadgeToMarkdown,
2810
+ StatCard: statCardToMarkdown,
2811
+ BalanceCard: balanceCardToMarkdown,
2812
+ Timeline: timelineToMarkdown,
2813
+ MessageFeed: messageFeedToMarkdown,
2814
+ PipelineBoard: pipelineBoardToMarkdown,
2815
+ TransactionPreview: transactionPreviewToMarkdown
1005
2816
  };
2817
+ var COMPONENT_KEYS = Object.keys(SCHEMA_BY_NAME);
1006
2818
  function escapeHtml(s) {
1007
2819
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1008
2820
  }
1009
- function fallbackMarkdown(componentName, locale) {
1010
- const heading = I18N_TITLE[componentName][locale];
1011
- const note = locale === "fr" ? "Rendu Markdown de secours \u2014 composant Mosaic charg\xE9 via MCP UI." : "Markdown fallback \u2014 Mosaic component rendered via MCP UI.";
1012
- return `# ${heading}
1013
-
1014
- ${note}
1015
- `;
1016
- }
1017
2821
  function buildHtml(componentName, props, locale) {
1018
2822
  const propsJson = escapeHtml(JSON.stringify(props));
1019
- const title = escapeHtml(I18N_TITLE[componentName][locale]);
1020
- return `<!DOCTYPE html><html lang="${locale}"><head><meta charset="utf-8"><title>${title}</title></head><body><div id="mosaic-root" data-component="${componentName}" data-props='${propsJson}'></div></body></html>`;
2823
+ const title = escapeHtml(t(`${componentName}.title`, locale));
2824
+ return `<!DOCTYPE html><html lang="${locale === "fr" ? "fr" : "en"}"><head><meta charset="utf-8"><title>${title}</title></head><body><div id="mosaic-root" data-component="${componentName}" data-props='${propsJson}'></div></body></html>`;
1021
2825
  }
1022
- function createMosaicResource(componentName, props, locale = "en") {
2826
+ function createMosaicResource(componentName, props, locale = "en", options = {}) {
1023
2827
  const schema = SCHEMA_BY_NAME[componentName];
1024
2828
  if (!schema) {
1025
2829
  throw new Error(`createMosaicResource: unknown component '${componentName}'`);
1026
2830
  }
1027
2831
  const validated = schema.parse(props);
1028
- const uri = `ui://mosaic/${componentName.toLowerCase()}`;
1029
- const html = buildHtml(componentName, validated, locale);
1030
- const fallback = fallbackMarkdown(componentName, locale);
2832
+ const embedded = options.includeSecrets ? validated : splitSecrets(componentName, validated, []).publicProps;
2833
+ const fallback = options.markdownFallback ?? MARKDOWN_BY_NAME[componentName](embedded, locale);
2834
+ return buildResource(componentName, embedded, locale, fallback, options.uri);
2835
+ }
2836
+ function buildResource(componentName, embedded, locale, fallback, uriOverride) {
2837
+ const uri = uriOverride ?? `ui://mosaic/${componentName.toLowerCase()}`;
2838
+ const html = buildHtml(componentName, embedded, locale);
1031
2839
  const resource = server.createUIResource({
1032
2840
  uri,
1033
2841
  content: { type: "rawHtml", htmlString: html },
@@ -1044,45 +2852,192 @@ function createMosaicResource(componentName, props, locale = "en") {
1044
2852
  resource.resource.mimeType = MOSAIC_MIME_TYPE;
1045
2853
  return resource;
1046
2854
  }
2855
+ var MOSAIC_NEVER_SERIALISE = {
2856
+ ProgressBar: [],
2857
+ ConfirmDialog: [],
2858
+ TableView: [],
2859
+ MarkdownRenderer: [],
2860
+ TokenDisplayOnceModal: ["token"],
2861
+ StatusBadge: [],
2862
+ StatCard: [],
2863
+ BalanceCard: [],
2864
+ Timeline: [],
2865
+ MessageFeed: [],
2866
+ PipelineBoard: [],
2867
+ TransactionPreview: []
2868
+ };
2869
+ var MosaicSecretLeakError = class extends Error {
2870
+ field;
2871
+ constructor(field, reason = "would be serialised into content[]") {
2872
+ super(`createMosaicToolResult: the value of "${field}" ${reason}`);
2873
+ this.name = "MosaicSecretLeakError";
2874
+ this.field = field;
2875
+ }
2876
+ };
2877
+ function splitSecrets(name, validated, extraFields) {
2878
+ const publicProps = { ...validated };
2879
+ const secrets = {};
2880
+ for (const f of extraFields) {
2881
+ if (!(f in validated))
2882
+ throw new Error(`createMosaicToolResult: unknown field "${f}" for ${name}`);
2883
+ }
2884
+ for (const field of [...MOSAIC_NEVER_SERIALISE[name], ...extraFields]) {
2885
+ if (field in publicProps) {
2886
+ secrets[field] = publicProps[field];
2887
+ delete publicProps[field];
2888
+ }
2889
+ }
2890
+ if (name === "TableView") {
2891
+ const columns = validated.columns ?? [];
2892
+ const sensitive = columns.filter((c) => c.sensitive).map((c) => c.key);
2893
+ if (sensitive.length > 0) {
2894
+ const cells = {};
2895
+ publicProps.rows = (validated.rows ?? []).map(
2896
+ (row, i) => {
2897
+ const rest = { ...row };
2898
+ for (const k of sensitive) {
2899
+ if (k in rest) {
2900
+ cells[`${i}.${k}`] = rest[k];
2901
+ delete rest[k];
2902
+ }
2903
+ }
2904
+ return rest;
2905
+ }
2906
+ );
2907
+ secrets.cells = cells;
2908
+ }
2909
+ }
2910
+ return { publicProps, secrets };
2911
+ }
2912
+ function fold(text) {
2913
+ return text.normalize("NFKC").toLowerCase();
2914
+ }
2915
+ function secretNeedles(secrets) {
2916
+ const out = [];
2917
+ for (const [field, v] of Object.entries(secrets)) {
2918
+ const values = field === "cells" && v && typeof v === "object" ? Object.values(v) : [v];
2919
+ for (const x of values) {
2920
+ const needle = typeof x === "string" ? x : typeof x === "number" || typeof x === "boolean" ? String(x) : x === null || x === void 0 ? "" : JSON.stringify(x);
2921
+ if (needle.length > 0) out.push({ field, needle });
2922
+ }
2923
+ }
2924
+ return out;
2925
+ }
1047
2926
  var MOSAIC_SUPPORTED_COMPONENTS = COMPONENT_KEYS;
2927
+ function createMosaicToolResult(componentName, props, locale = "en", options = {}) {
2928
+ const schema = SCHEMA_BY_NAME[componentName];
2929
+ if (!schema) {
2930
+ throw new Error(`createMosaicResource: unknown component '${componentName}'`);
2931
+ }
2932
+ const validated = schema.parse(props);
2933
+ const { publicProps, secrets } = splitSecrets(
2934
+ componentName,
2935
+ validated,
2936
+ options.neverSerialise ?? []
2937
+ );
2938
+ let markdown;
2939
+ try {
2940
+ markdown = MARKDOWN_BY_NAME[componentName](publicProps, locale);
2941
+ } catch (err) {
2942
+ const declared = Object.keys(secrets).filter((k) => k !== "cells")[0];
2943
+ if (declared && err instanceof zod.ZodError) {
2944
+ throw new MosaicSecretLeakError(
2945
+ declared,
2946
+ "is required by the fallback renderer and cannot be declared secret"
2947
+ );
2948
+ }
2949
+ throw err;
2950
+ }
2951
+ const ui = buildResource(componentName, publicProps, locale, markdown);
2952
+ const content = [
2953
+ { type: "text", text: markdown },
2954
+ ui
2955
+ ];
2956
+ const serialised = fold(JSON.stringify(content));
2957
+ for (const { field, needle } of secretNeedles(secrets)) {
2958
+ if (serialised.includes(fold(needle)) || serialised.includes(fold(JSON.stringify(needle).slice(1, -1)))) {
2959
+ throw new MosaicSecretLeakError(field);
2960
+ }
2961
+ }
2962
+ return Object.keys(secrets).length > 0 ? { content, _meta: { "mosaic/secrets": secrets } } : { content };
2963
+ }
1048
2964
 
1049
2965
  exports.AlertPropsSchema = AlertPropsSchema;
2966
+ exports.BalanceCard = BalanceCard;
2967
+ exports.BalanceCardPropsSchema = BalanceCardPropsSchema;
1050
2968
  exports.ConfirmDialog = ConfirmDialog;
1051
2969
  exports.ConfirmDialogPropsSchema = ConfirmDialogPropsSchema;
1052
2970
  exports.EmptyStatePropsSchema = EmptyStatePropsSchema;
1053
2971
  exports.MCP_UI_CAPABILITY_KEY = MCP_UI_CAPABILITY_KEY;
1054
2972
  exports.MOSAIC_MIME_TYPE = MOSAIC_MIME_TYPE;
2973
+ exports.MOSAIC_NEVER_SERIALISE = MOSAIC_NEVER_SERIALISE;
1055
2974
  exports.MOSAIC_STREAM_CHUNK_TYPE = MOSAIC_STREAM_CHUNK_TYPE;
1056
2975
  exports.MOSAIC_SUPPORTED_COMPONENTS = MOSAIC_SUPPORTED_COMPONENTS;
1057
2976
  exports.MarkdownRenderer = MarkdownRenderer;
1058
2977
  exports.MarkdownRendererPropsSchema = MarkdownRendererPropsSchema;
2978
+ exports.MessageFeed = MessageFeed;
2979
+ exports.MessageFeedPropsSchema = MessageFeedPropsSchema;
2980
+ exports.MosaicSecretLeakError = MosaicSecretLeakError;
2981
+ exports.PipelineBoard = PipelineBoard;
2982
+ exports.PipelineBoardPropsSchema = PipelineBoardPropsSchema;
1059
2983
  exports.ProgressBar = ProgressBar;
1060
2984
  exports.ProgressBarPropsSchema = ProgressBarPropsSchema;
2985
+ exports.SIZE_CHANGED_METHOD = SIZE_CHANGED_METHOD;
1061
2986
  exports.Skeleton = Skeleton;
1062
2987
  exports.SkeletonPropsSchema = SkeletonPropsSchema;
2988
+ exports.StatCard = StatCard;
2989
+ exports.StatCardPropsSchema = StatCardPropsSchema;
1063
2990
  exports.StatusBadge = StatusBadge;
1064
2991
  exports.StatusBadgePropsSchema = StatusBadgePropsSchema;
1065
2992
  exports.StreamingTableView = StreamingTableView;
1066
2993
  exports.TableView = TableView;
1067
2994
  exports.TableViewPropsSchema = TableViewPropsSchema;
1068
2995
  exports.TabsPropsSchema = TabsPropsSchema;
2996
+ exports.Timeline = Timeline;
2997
+ exports.TimelinePropsSchema = TimelinePropsSchema;
1069
2998
  exports.ToastPropsSchema = ToastPropsSchema;
1070
2999
  exports.TokenDisplayOnceModal = TokenDisplayOnceModal;
1071
3000
  exports.TokenDisplayOnceModalPropsSchema = TokenDisplayOnceModalPropsSchema;
3001
+ exports.TransactionPreview = TransactionPreview;
3002
+ exports.TransactionPreviewPropsSchema = TransactionPreviewPropsSchema;
3003
+ exports.balanceCardToMarkdown = balanceCardToMarkdown;
3004
+ exports.compareDecimalStrings = compareDecimalStrings;
3005
+ exports.confirmDialogToMarkdown = confirmDialogToMarkdown;
1072
3006
  exports.createMosaicResource = createMosaicResource;
3007
+ exports.createMosaicToolResult = createMosaicToolResult;
1073
3008
  exports.createPostMessageObservable = createPostMessageObservable;
3009
+ exports.markdownRendererToMarkdown = markdownRendererToMarkdown;
3010
+ exports.messageFeedToMarkdown = messageFeedToMarkdown;
3011
+ exports.nextSort = nextSort;
3012
+ exports.pipelineBoardToMarkdown = pipelineBoardToMarkdown;
3013
+ exports.postSizeChanged = postSizeChanged;
3014
+ exports.progressBarToMarkdown = progressBarToMarkdown;
1074
3015
  exports.resolveSkeletonDimensions = resolveSkeletonDimensions;
3016
+ exports.sortItems = sortItems;
3017
+ exports.startSizeReporting = startSizeReporting;
3018
+ exports.statCardToMarkdown = statCardToMarkdown;
3019
+ exports.statusBadgeToMarkdown = statusBadgeToMarkdown;
3020
+ exports.tableViewToMarkdown = tableViewToMarkdown;
3021
+ exports.timelineToMarkdown = timelineToMarkdown;
3022
+ exports.tokenDisplayOnceModalToMarkdown = tokenDisplayOnceModalToMarkdown;
3023
+ exports.transactionPreviewToMarkdown = transactionPreviewToMarkdown;
1075
3024
  exports.useStreamingHydration = useStreamingHydration;
1076
3025
  exports.validateAlertProps = validateAlertProps;
3026
+ exports.validateBalanceCardProps = validateBalanceCardProps;
1077
3027
  exports.validateConfirmDialogProps = validateConfirmDialogProps;
1078
3028
  exports.validateEmptyStateProps = validateEmptyStateProps;
1079
3029
  exports.validateMarkdownRendererProps = validateProps2;
3030
+ exports.validateMessageFeedProps = validateMessageFeedProps;
3031
+ exports.validatePipelineBoardProps = validatePipelineBoardProps;
1080
3032
  exports.validateProps = validateProps;
1081
3033
  exports.validateSkeletonProps = validateSkeletonProps;
3034
+ exports.validateStatCardProps = validateStatCardProps;
1082
3035
  exports.validateStatusBadgeProps = validateProps3;
1083
3036
  exports.validateTableViewProps = validateTableViewProps;
1084
3037
  exports.validateTabsProps = validateTabsProps;
3038
+ exports.validateTimelineProps = validateTimelineProps;
1085
3039
  exports.validateToastProps = validateToastProps;
1086
3040
  exports.validateTokenDisplayOnceModalProps = validateTokenDisplayOnceModalProps;
3041
+ exports.validateTransactionPreviewProps = validateTransactionPreviewProps;
1087
3042
  //# sourceMappingURL=index.cjs.map
1088
3043
  //# sourceMappingURL=index.cjs.map