@sableui/react 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (642) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +315 -0
  3. package/CONTRIBUTING.md +16 -10
  4. package/DESIGN.md +3 -3
  5. package/README.md +3 -3
  6. package/bin/sable-lint.mjs +18 -0
  7. package/bin/sable.mjs +470 -62
  8. package/dist/blocks/api-keys-block.js +44 -0
  9. package/dist/blocks/api-keys-block.meta.js +21 -0
  10. package/dist/blocks/chat-thread-block.js +17 -0
  11. package/dist/blocks/chat-thread-block.meta.js +20 -0
  12. package/dist/blocks/command-palette-block.js +24 -0
  13. package/dist/blocks/command-palette-block.meta.js +21 -0
  14. package/dist/blocks/cta-band-block.js +27 -0
  15. package/dist/blocks/cta-band-block.meta.js +20 -0
  16. package/dist/blocks/danger-zone-block.js +39 -0
  17. package/dist/blocks/danger-zone-block.meta.js +21 -0
  18. package/dist/blocks/device-comparison-block.js +69 -0
  19. package/dist/blocks/device-comparison-block.meta.js +21 -0
  20. package/dist/blocks/event-card-block.js +40 -0
  21. package/dist/blocks/event-card-block.meta.js +21 -0
  22. package/dist/blocks/faq-block.js +38 -0
  23. package/dist/blocks/faq-block.meta.js +20 -0
  24. package/dist/blocks/feature-grid-block.js +38 -0
  25. package/dist/blocks/feature-grid-block.meta.js +21 -0
  26. package/dist/blocks/feedback-form-block.js +30 -0
  27. package/dist/blocks/feedback-form-block.meta.js +21 -0
  28. package/dist/blocks/goal-progress-block.js +24 -0
  29. package/dist/blocks/goal-progress-block.meta.js +21 -0
  30. package/dist/blocks/hero-block.js +33 -0
  31. package/dist/blocks/hero-block.meta.js +21 -0
  32. package/dist/blocks/index.js +162 -1964
  33. package/dist/blocks/invite-team-block.js +36 -0
  34. package/dist/blocks/invite-team-block.meta.js +20 -0
  35. package/dist/blocks/invoice-history-block.js +41 -0
  36. package/dist/blocks/invoice-history-block.meta.js +21 -0
  37. package/dist/blocks/kpi-sparkline-block.js +29 -0
  38. package/dist/blocks/kpi-sparkline-block.meta.js +20 -0
  39. package/dist/blocks/media-player-block.js +68 -0
  40. package/dist/blocks/media-player-block.meta.js +21 -0
  41. package/dist/blocks/newsletter-block.js +35 -0
  42. package/dist/blocks/newsletter-block.meta.js +21 -0
  43. package/dist/blocks/not-found-block.js +27 -0
  44. package/dist/blocks/not-found-block.meta.js +20 -0
  45. package/dist/blocks/notification-list-block.js +32 -0
  46. package/dist/blocks/notification-list-block.meta.js +21 -0
  47. package/dist/blocks/notification-settings-block.js +25 -0
  48. package/dist/blocks/notification-settings-block.meta.js +20 -0
  49. package/dist/blocks/order-tracking-block.js +26 -0
  50. package/dist/blocks/order-tracking-block.meta.js +20 -0
  51. package/dist/blocks/pricing-card-block.js +46 -0
  52. package/dist/blocks/pricing-card-block.meta.js +21 -0
  53. package/dist/blocks/pricing-table-block.js +88 -0
  54. package/dist/blocks/pricing-table-block.meta.js +20 -0
  55. package/dist/blocks/product-card-block.js +26 -0
  56. package/dist/blocks/product-card-block.meta.js +21 -0
  57. package/dist/blocks/profile-card-block.js +58 -0
  58. package/dist/blocks/profile-card-block.meta.js +21 -0
  59. package/dist/blocks/profile-form-block.js +70 -0
  60. package/dist/blocks/profile-form-block.meta.js +21 -0
  61. package/dist/blocks/revenue-breakdown-block.js +38 -0
  62. package/dist/blocks/revenue-breakdown-block.meta.js +21 -0
  63. package/dist/blocks/sign-in-block.js +38 -0
  64. package/dist/blocks/sign-in-block.meta.js +21 -0
  65. package/dist/blocks/sign-up-block.js +48 -0
  66. package/dist/blocks/sign-up-block.meta.js +21 -0
  67. package/dist/blocks/site-footer-block.js +54 -0
  68. package/dist/blocks/site-footer-block.meta.js +20 -0
  69. package/dist/blocks/stat-row-block.js +28 -0
  70. package/dist/blocks/stat-row-block.meta.js +21 -0
  71. package/dist/blocks/steps-block.js +37 -0
  72. package/dist/blocks/steps-block.meta.js +20 -0
  73. package/dist/blocks/task-list-block.js +52 -0
  74. package/dist/blocks/task-list-block.meta.js +21 -0
  75. package/dist/blocks/team-members-block.js +49 -0
  76. package/dist/blocks/team-members-block.meta.js +21 -0
  77. package/dist/blocks/testimonial-block.js +49 -0
  78. package/dist/blocks/testimonial-block.meta.js +20 -0
  79. package/dist/blocks/traffic-sources-block.js +39 -0
  80. package/dist/blocks/traffic-sources-block.meta.js +21 -0
  81. package/dist/blocks/traffic-trend-block.js +61 -0
  82. package/dist/blocks/traffic-trend-block.meta.js +21 -0
  83. package/dist/blocks/upload-list-block.js +39 -0
  84. package/dist/blocks/upload-list-block.meta.js +21 -0
  85. package/dist/blocks/verify-code-block.js +28 -0
  86. package/dist/blocks/verify-code-block.meta.js +21 -0
  87. package/dist/blocks/wallet-block.js +48 -0
  88. package/dist/blocks/wallet-block.meta.js +21 -0
  89. package/dist/components/accordion/accordion.js +63 -0
  90. package/dist/components/accordion/accordion.meta.js +155 -0
  91. package/dist/components/accordion/index.js +15 -0
  92. package/dist/components/app-header/app-header.js +30 -0
  93. package/dist/components/app-header/app-header.meta.js +88 -0
  94. package/dist/components/app-header/index.js +7 -0
  95. package/dist/components/app-shell/app-shell-dock.js +37 -0
  96. package/dist/components/app-shell/app-shell.js +67 -0
  97. package/dist/components/app-shell/app-shell.meta.js +176 -0
  98. package/dist/components/app-shell/index.js +7 -0
  99. package/dist/components/area-chart/area-chart.js +10 -0
  100. package/dist/components/area-chart/area-chart.meta.js +218 -0
  101. package/dist/components/area-chart/index.js +7 -0
  102. package/dist/components/attachment/attachment.js +176 -0
  103. package/dist/components/attachment/attachment.meta.js +179 -0
  104. package/dist/components/attachment/attachment.variants.js +47 -0
  105. package/dist/components/attachment/index.js +7 -0
  106. package/dist/components/avatar/avatar.js +192 -0
  107. package/dist/components/avatar/avatar.meta.js +233 -0
  108. package/dist/components/avatar/avatar.variants.js +44 -0
  109. package/dist/components/avatar/index.js +22 -0
  110. package/dist/components/badge/badge.js +26 -0
  111. package/dist/components/badge/badge.meta.js +171 -0
  112. package/dist/components/badge/badge.variants.js +103 -0
  113. package/dist/components/badge/index.js +11 -0
  114. package/dist/components/banner/banner.js +76 -0
  115. package/dist/components/banner/banner.meta.js +172 -0
  116. package/dist/components/banner/banner.variants.js +44 -0
  117. package/dist/components/banner/index.js +9 -0
  118. package/dist/components/bar-chart/bar-chart.js +10 -0
  119. package/dist/components/bar-chart/bar-chart.meta.js +245 -0
  120. package/dist/components/bar-chart/index.js +7 -0
  121. package/dist/components/bottom-nav/bottom-nav.js +63 -0
  122. package/dist/components/bottom-nav/bottom-nav.meta.js +95 -0
  123. package/dist/components/bottom-nav/index.js +11 -0
  124. package/dist/components/breadcrumbs/breadcrumbs.js +90 -0
  125. package/dist/components/breadcrumbs/breadcrumbs.meta.js +111 -0
  126. package/dist/components/breadcrumbs/index.js +17 -0
  127. package/dist/components/browser/browser.js +39 -0
  128. package/dist/components/browser/browser.meta.js +80 -0
  129. package/dist/components/browser/index.js +7 -0
  130. package/dist/components/bubble/bubble.js +110 -0
  131. package/dist/components/bubble/bubble.meta.js +158 -0
  132. package/dist/components/bubble/bubble.variants.js +90 -0
  133. package/dist/components/bubble/index.js +13 -0
  134. package/dist/components/button/button.js +87 -0
  135. package/dist/components/button/button.meta.js +302 -0
  136. package/dist/components/button/button.variants.js +137 -0
  137. package/dist/components/button/index.js +9 -0
  138. package/dist/components/calendar/calendar.js +89 -0
  139. package/dist/components/calendar/calendar.meta.js +136 -0
  140. package/dist/components/calendar/index.js +7 -0
  141. package/dist/components/card/card.js +203 -0
  142. package/dist/components/card/card.meta.js +212 -0
  143. package/dist/components/card/index.js +23 -0
  144. package/dist/components/carousel/carousel.js +428 -0
  145. package/dist/components/carousel/carousel.meta.js +100 -0
  146. package/dist/components/carousel/index.js +7 -0
  147. package/dist/components/chat-composer/chat-composer.js +174 -0
  148. package/dist/components/chat-composer/chat-composer.meta.js +241 -0
  149. package/dist/components/chat-composer/chat-composer.variants.js +17 -0
  150. package/dist/components/chat-composer/index.js +7 -0
  151. package/dist/components/checkbox/checkbox-mark.js +28 -0
  152. package/dist/components/checkbox/checkbox.js +30 -0
  153. package/dist/components/checkbox/checkbox.meta.js +127 -0
  154. package/dist/components/checkbox/index.js +7 -0
  155. package/dist/components/checkbox-card/checkbox-card.js +94 -0
  156. package/dist/components/checkbox-card/checkbox-card.meta.js +154 -0
  157. package/dist/components/checkbox-card/checkbox-card.variants.js +43 -0
  158. package/dist/components/checkbox-card/index.js +11 -0
  159. package/dist/components/code/code.js +19 -0
  160. package/dist/components/code/code.meta.js +71 -0
  161. package/dist/components/code/index.js +7 -0
  162. package/dist/components/code-block/code-block.js +84 -0
  163. package/dist/components/code-block/code-block.meta.js +103 -0
  164. package/dist/components/code-block/index.js +8 -0
  165. package/dist/components/collapsible/collapsible.js +38 -0
  166. package/dist/components/collapsible/collapsible.meta.js +117 -0
  167. package/dist/components/collapsible/index.js +15 -0
  168. package/dist/components/color-field/color-field.js +293 -0
  169. package/dist/components/color-field/color-field.meta.js +142 -0
  170. package/dist/components/color-field/color.js +69 -0
  171. package/dist/components/color-field/index.js +15 -0
  172. package/dist/components/combobox/combobox.js +486 -0
  173. package/dist/components/combobox/combobox.meta.js +217 -0
  174. package/dist/components/combobox/index.js +37 -0
  175. package/dist/components/command-menu/command-menu-trigger.js +122 -0
  176. package/dist/components/command-menu/command-menu.js +433 -0
  177. package/dist/components/command-menu/command-menu.meta.js +279 -0
  178. package/dist/components/command-menu/index.js +21 -0
  179. package/dist/components/context-card/context-card.js +62 -0
  180. package/dist/components/context-card/context-card.meta.js +106 -0
  181. package/dist/components/context-card/index.js +17 -0
  182. package/dist/components/context-menu/context-menu.js +92 -0
  183. package/dist/components/context-menu/context-menu.meta.js +138 -0
  184. package/dist/components/context-menu/index.js +23 -0
  185. package/dist/components/data-table/data-table.js +142 -0
  186. package/dist/components/data-table/data-table.meta.js +146 -0
  187. package/dist/components/data-table/index.js +9 -0
  188. package/dist/components/date-picker/date-picker.js +196 -0
  189. package/dist/components/date-picker/date-picker.meta.js +164 -0
  190. package/dist/components/date-picker/index.js +7 -0
  191. package/dist/components/date-tile/date-tile.js +29 -0
  192. package/dist/components/date-tile/date-tile.meta.js +72 -0
  193. package/dist/components/date-tile/index.js +7 -0
  194. package/dist/components/destructive-action-modal/destructive-action-modal.js +130 -0
  195. package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +136 -0
  196. package/dist/components/destructive-action-modal/index.js +9 -0
  197. package/dist/components/dialog/dialog.js +207 -0
  198. package/dist/components/dialog/dialog.meta.js +239 -0
  199. package/dist/components/dialog/index.js +45 -0
  200. package/dist/components/disclosure-picker/disclosure-picker.js +417 -0
  201. package/dist/components/disclosure-picker/disclosure-picker.meta.js +213 -0
  202. package/dist/components/disclosure-picker/index.js +21 -0
  203. package/dist/components/empty-state/empty-state.js +52 -0
  204. package/dist/components/empty-state/empty-state.meta.js +158 -0
  205. package/dist/components/empty-state/index.js +7 -0
  206. package/dist/components/entity/entity.js +70 -0
  207. package/dist/components/entity/entity.meta.js +108 -0
  208. package/dist/components/entity/entity.variants.js +16 -0
  209. package/dist/components/entity/index.js +7 -0
  210. package/dist/components/feedback/feedback.js +93 -0
  211. package/dist/components/feedback/feedback.meta.js +109 -0
  212. package/dist/components/feedback/index.js +7 -0
  213. package/dist/components/fieldset/fieldset.js +80 -0
  214. package/dist/components/fieldset/fieldset.meta.js +160 -0
  215. package/dist/components/fieldset/index.js +17 -0
  216. package/dist/components/file-dropzone/file-dropzone.js +193 -0
  217. package/dist/components/file-dropzone/file-dropzone.meta.js +210 -0
  218. package/dist/components/file-dropzone/file-dropzone.variants.js +26 -0
  219. package/dist/components/file-dropzone/index.js +13 -0
  220. package/dist/components/font-picker/font-picker.js +77 -0
  221. package/dist/components/font-picker/font-picker.meta.js +119 -0
  222. package/dist/components/font-picker/index.js +7 -0
  223. package/dist/components/form/form.js +145 -0
  224. package/dist/components/form/form.meta.js +142 -0
  225. package/dist/components/form/index.js +11 -0
  226. package/dist/components/gliding-highlight/gliding-highlight.js +50 -0
  227. package/dist/components/gliding-highlight/gliding-highlight.meta.js +135 -0
  228. package/dist/components/gliding-highlight/index.js +9 -0
  229. package/dist/components/gradient-art/gradient-art.js +168 -0
  230. package/dist/components/gradient-art/gradient-art.meta.js +81 -0
  231. package/dist/components/gradient-art/index.js +7 -0
  232. package/dist/components/gradient-art/renderer.js +336 -0
  233. package/dist/components/inline-status/index.js +7 -0
  234. package/dist/components/inline-status/inline-status.js +33 -0
  235. package/dist/components/inline-status/inline-status.meta.js +83 -0
  236. package/dist/components/inline-status/inline-status.variants.js +18 -0
  237. package/dist/components/input/index.js +7 -0
  238. package/dist/components/input/input.js +33 -0
  239. package/dist/components/input/input.meta.js +155 -0
  240. package/dist/components/kbd/index.js +8 -0
  241. package/dist/components/kbd/kbd.js +54 -0
  242. package/dist/components/kbd/kbd.meta.js +95 -0
  243. package/dist/components/label/index.js +7 -0
  244. package/dist/components/label/label.js +51 -0
  245. package/dist/components/label/label.meta.js +98 -0
  246. package/dist/components/line-chart/index.js +7 -0
  247. package/dist/components/line-chart/line-chart.js +10 -0
  248. package/dist/components/line-chart/line-chart.meta.js +222 -0
  249. package/dist/components/marker/index.js +7 -0
  250. package/dist/components/marker/marker.js +39 -0
  251. package/dist/components/marker/marker.meta.js +132 -0
  252. package/dist/components/marker/marker.variants.js +25 -0
  253. package/dist/components/marquee/index.js +7 -0
  254. package/dist/components/marquee/marquee.js +146 -0
  255. package/dist/components/marquee/marquee.meta.js +106 -0
  256. package/dist/components/media-image/index.js +7 -0
  257. package/dist/components/media-image/media-image.js +56 -0
  258. package/dist/components/media-image/media-image.meta.js +92 -0
  259. package/dist/components/media-tile/index.js +7 -0
  260. package/dist/components/media-tile/media-tile.js +135 -0
  261. package/dist/components/media-tile/media-tile.meta.js +133 -0
  262. package/dist/components/media-tile/media-tile.variants.js +48 -0
  263. package/dist/components/menu/index.js +33 -0
  264. package/dist/components/menu/menu.js +168 -0
  265. package/dist/components/menu/menu.meta.js +176 -0
  266. package/dist/components/message/index.js +7 -0
  267. package/dist/components/message/message.js +83 -0
  268. package/dist/components/message/message.meta.js +150 -0
  269. package/dist/components/message/message.variants.js +41 -0
  270. package/dist/components/message-scroller/index.js +9 -0
  271. package/dist/components/message-scroller/message-scroller.js +300 -0
  272. package/dist/components/message-scroller/message-scroller.meta.js +172 -0
  273. package/dist/components/meter/index.js +20 -0
  274. package/dist/components/meter/meter.js +112 -0
  275. package/dist/components/meter/meter.meta.js +227 -0
  276. package/dist/components/meter/meter.variants.js +61 -0
  277. package/dist/components/middle-truncate/index.js +7 -0
  278. package/dist/components/middle-truncate/middle-truncate.js +28 -0
  279. package/dist/components/middle-truncate/middle-truncate.meta.js +77 -0
  280. package/dist/components/note/index.js +9 -0
  281. package/dist/components/note/note.js +73 -0
  282. package/dist/components/note/note.meta.js +112 -0
  283. package/dist/components/note/note.variants.js +22 -0
  284. package/dist/components/notification-badge/index.js +15 -0
  285. package/dist/components/notification-badge/notification-badge.js +48 -0
  286. package/dist/components/notification-badge/notification-badge.meta.js +107 -0
  287. package/dist/components/notification-badge/notification-badge.variants.js +57 -0
  288. package/dist/components/number-field/index.js +7 -0
  289. package/dist/components/number-field/number-field.js +70 -0
  290. package/dist/components/number-field/number-field.meta.js +109 -0
  291. package/dist/components/otp-field/index.js +7 -0
  292. package/dist/components/otp-field/otp-field.js +70 -0
  293. package/dist/components/otp-field/otp-field.meta.js +197 -0
  294. package/dist/components/overflow-menu/index.js +7 -0
  295. package/dist/components/overflow-menu/overflow-menu.js +26 -0
  296. package/dist/components/overflow-menu/overflow-menu.meta.js +100 -0
  297. package/dist/components/page-header/index.js +7 -0
  298. package/dist/components/page-header/page-header.js +34 -0
  299. package/dist/components/page-header/page-header.meta.js +104 -0
  300. package/dist/components/page-loader/index.js +7 -0
  301. package/dist/components/page-loader/page-loader.js +37 -0
  302. package/dist/components/page-loader/page-loader.meta.js +85 -0
  303. package/dist/components/pagination/index.js +7 -0
  304. package/dist/components/pagination/pagination.js +105 -0
  305. package/dist/components/pagination/pagination.meta.js +141 -0
  306. package/dist/components/panel-group/index.js +10 -0
  307. package/dist/components/panel-group/panel-group.js +168 -0
  308. package/dist/components/panel-group/panel-group.meta.js +153 -0
  309. package/dist/components/panel-section/index.js +9 -0
  310. package/dist/components/panel-section/panel-section.js +115 -0
  311. package/dist/components/panel-section/panel-section.meta.js +156 -0
  312. package/dist/components/phone/index.js +7 -0
  313. package/dist/components/phone/phone.js +50 -0
  314. package/dist/{chunk-KFZ43RKU.js → components/phone/phone.meta.js} +3 -50
  315. package/dist/{chunk-NEQNXI2G.js → components/phone/status-icons.js} +4 -4
  316. package/dist/components/pie-chart/index.js +7 -0
  317. package/dist/components/pie-chart/pie-chart.js +191 -0
  318. package/dist/components/pie-chart/pie-chart.meta.js +210 -0
  319. package/dist/components/pill/index.js +9 -0
  320. package/dist/components/pill/pill.js +24 -0
  321. package/dist/components/pill/pill.meta.js +108 -0
  322. package/dist/components/pill/pill.variants.js +65 -0
  323. package/dist/components/popover/index.js +23 -0
  324. package/dist/components/popover/popover.js +100 -0
  325. package/dist/components/popover/popover.meta.js +136 -0
  326. package/dist/components/progress/index.js +19 -0
  327. package/dist/components/progress/progress.js +70 -0
  328. package/dist/components/progress/progress.meta.js +171 -0
  329. package/dist/components/progress/progress.variants.js +17 -0
  330. package/dist/components/radar-chart/index.js +7 -0
  331. package/dist/components/radar-chart/radar-chart.js +161 -0
  332. package/dist/components/radar-chart/radar-chart.meta.js +204 -0
  333. package/dist/components/radial-chart/index.js +7 -0
  334. package/dist/components/radial-chart/radial-chart.js +175 -0
  335. package/dist/components/radial-chart/radial-chart.meta.js +216 -0
  336. package/dist/components/radio/index.js +8 -0
  337. package/dist/components/radio/radio.js +39 -0
  338. package/dist/components/radio/radio.meta.js +104 -0
  339. package/dist/components/radio-card/index.js +7 -0
  340. package/dist/components/radio-card/radio-card.js +81 -0
  341. package/dist/components/radio-card/radio-card.meta.js +144 -0
  342. package/dist/components/radio-card/radio-card.variants.js +43 -0
  343. package/dist/components/relative-time/index.js +7 -0
  344. package/dist/components/relative-time/relative-time.js +47 -0
  345. package/dist/components/relative-time/relative-time.meta.js +77 -0
  346. package/dist/components/scroller/index.js +7 -0
  347. package/dist/components/scroller/scroller.js +111 -0
  348. package/dist/components/scroller/scroller.meta.js +118 -0
  349. package/dist/components/scrub-field/index.js +7 -0
  350. package/dist/components/scrub-field/scrub-field.js +124 -0
  351. package/dist/components/scrub-field/scrub-field.meta.js +139 -0
  352. package/dist/components/search-input/index.js +7 -0
  353. package/dist/components/search-input/search-input.js +91 -0
  354. package/dist/components/search-input/search-input.meta.js +129 -0
  355. package/dist/components/section-header/index.js +7 -0
  356. package/dist/components/section-header/section-header.js +39 -0
  357. package/dist/components/section-header/section-header.meta.js +174 -0
  358. package/dist/components/segmented-bar/index.js +14 -0
  359. package/dist/components/segmented-bar/segmented-bar.js +26 -0
  360. package/dist/components/segmented-bar/segmented-bar.meta.js +108 -0
  361. package/dist/components/segmented-bar/segmented-bar.variants.js +39 -0
  362. package/dist/components/select/index.js +25 -0
  363. package/dist/components/select/select.js +191 -0
  364. package/dist/components/select/select.meta.js +189 -0
  365. package/dist/components/separator/index.js +7 -0
  366. package/dist/components/separator/separator.js +42 -0
  367. package/dist/components/separator/separator.meta.js +84 -0
  368. package/dist/components/settings-menu/index.js +11 -0
  369. package/dist/components/settings-menu/settings-menu.js +101 -0
  370. package/dist/components/settings-menu/settings-menu.meta.js +177 -0
  371. package/dist/components/settings-menu/settings-menu.variants.js +15 -0
  372. package/dist/components/sheet/index.js +29 -0
  373. package/dist/components/sheet/sheet.js +121 -0
  374. package/dist/components/sheet/sheet.meta.js +167 -0
  375. package/dist/components/sheet/sheet.variants.js +21 -0
  376. package/dist/components/show-more/index.js +7 -0
  377. package/dist/components/show-more/show-more.js +87 -0
  378. package/dist/components/show-more/show-more.meta.js +79 -0
  379. package/dist/components/side-nav/index.js +13 -0
  380. package/dist/components/side-nav/side-nav.js +75 -0
  381. package/dist/components/side-nav/side-nav.meta.js +139 -0
  382. package/dist/components/side-nav/side-nav.variants.js +34 -0
  383. package/dist/components/sidebar/index.js +21 -0
  384. package/dist/components/sidebar/sidebar.js +196 -0
  385. package/dist/components/sidebar/sidebar.meta.js +118 -0
  386. package/dist/components/skeleton/index.js +7 -0
  387. package/dist/components/skeleton/skeleton.js +16 -0
  388. package/dist/components/skeleton/skeleton.meta.js +90 -0
  389. package/dist/components/slider/index.js +21 -0
  390. package/dist/components/slider/slider.js +115 -0
  391. package/dist/components/slider/slider.meta.js +214 -0
  392. package/dist/components/sparkline/index.js +7 -0
  393. package/dist/components/sparkline/sparkline.js +84 -0
  394. package/dist/components/sparkline/sparkline.meta.js +147 -0
  395. package/dist/components/spinner/index.js +7 -0
  396. package/dist/components/spinner/spinner.js +59 -0
  397. package/dist/components/spinner/spinner.meta.js +118 -0
  398. package/dist/components/status-bar/index.js +9 -0
  399. package/dist/components/status-bar/status-bar.js +40 -0
  400. package/dist/components/status-bar/status-bar.meta.js +117 -0
  401. package/dist/components/status-dot/index.js +9 -0
  402. package/dist/components/status-dot/status-dot.js +40 -0
  403. package/dist/components/status-dot/status-dot.meta.js +113 -0
  404. package/dist/components/status-dot/status-dot.variants.js +28 -0
  405. package/dist/components/stepper/index.js +17 -0
  406. package/dist/components/stepper/stepper.js +161 -0
  407. package/dist/components/stepper/stepper.meta.js +190 -0
  408. package/dist/components/stepper/stepper.variants.js +72 -0
  409. package/dist/components/streaming-text/index.js +7 -0
  410. package/dist/components/streaming-text/streaming-text.js +55 -0
  411. package/dist/components/streaming-text/streaming-text.meta.js +147 -0
  412. package/dist/components/switch/index.js +7 -0
  413. package/dist/components/switch/switch.js +26 -0
  414. package/dist/components/switch/switch.meta.js +133 -0
  415. package/dist/components/table/index.js +23 -0
  416. package/dist/components/table/table.js +117 -0
  417. package/dist/components/table/table.meta.js +143 -0
  418. package/dist/components/tabs/index.js +17 -0
  419. package/dist/components/tabs/tabs.js +58 -0
  420. package/dist/components/tabs/tabs.meta.js +120 -0
  421. package/dist/components/textarea/index.js +7 -0
  422. package/dist/components/textarea/textarea.js +38 -0
  423. package/dist/components/textarea/textarea.meta.js +117 -0
  424. package/dist/components/theme-selector/index.js +11 -0
  425. package/dist/components/theme-selector/theme-selector.js +145 -0
  426. package/dist/components/theme-selector/theme-selector.meta.js +181 -0
  427. package/dist/components/theme-switcher/index.js +7 -0
  428. package/dist/components/theme-switcher/theme-switcher.js +106 -0
  429. package/dist/components/theme-switcher/theme-switcher.meta.js +99 -0
  430. package/dist/components/thinking-orb/index.js +9 -0
  431. package/dist/components/thinking-orb/thinking-orb.js +126 -0
  432. package/dist/components/thinking-orb/thinking-orb.meta.js +142 -0
  433. package/dist/components/thinking-orb/thinking-orb.render.js +252 -0
  434. package/dist/components/toast/index.js +29 -0
  435. package/dist/components/toast/toast.js +254 -0
  436. package/dist/components/toast/toast.meta.js +261 -0
  437. package/dist/components/toggle/index.js +13 -0
  438. package/dist/components/toggle/toggle.js +51 -0
  439. package/dist/components/toggle/toggle.meta.js +129 -0
  440. package/dist/components/toolbar/index.js +27 -0
  441. package/dist/components/toolbar/toolbar.js +80 -0
  442. package/dist/components/toolbar/toolbar.meta.js +272 -0
  443. package/dist/components/toolbar/toolbar.variants.js +16 -0
  444. package/dist/components/tooltip/index.js +21 -0
  445. package/dist/components/tooltip/tooltip.js +74 -0
  446. package/dist/components/tooltip/tooltip.meta.js +156 -0
  447. package/dist/components/tree/index.js +12 -0
  448. package/dist/components/tree/tree.js +364 -0
  449. package/dist/components/tree/tree.meta.js +334 -0
  450. package/dist/components/video/index.js +7 -0
  451. package/dist/components/video/video.js +59 -0
  452. package/dist/components/video/video.meta.js +81 -0
  453. package/dist/components/workspace/index.js +9 -0
  454. package/dist/components/workspace/workspace.js +79 -0
  455. package/dist/components/workspace/workspace.meta.js +183 -0
  456. package/dist/foundations/accessibility.js +107 -0
  457. package/dist/foundations/index.js +25 -792
  458. package/dist/foundations/motion.js +135 -0
  459. package/dist/foundations/scrolling.js +62 -0
  460. package/dist/foundations/shape.js +113 -0
  461. package/dist/foundations/spacing.js +121 -0
  462. package/dist/foundations/surfaces.js +107 -0
  463. package/dist/foundations/types.js +0 -0
  464. package/dist/foundations/typography.js +152 -0
  465. package/dist/icon/Icon.js +30 -0
  466. package/dist/icon/IconProvider.js +27 -0
  467. package/dist/icon/compose.js +13 -0
  468. package/dist/icon/index.d.ts +13 -5
  469. package/dist/icon/index.js +13 -2
  470. package/dist/icon/registry.js +0 -0
  471. package/dist/icon-sets/brands.generated.js +123 -0
  472. package/dist/icon-sets/brands.js +10 -123
  473. package/dist/icon-sets/reicon.d.ts +3 -1
  474. package/dist/icon-sets/reicon.js +209 -1
  475. package/dist/index.d.ts +618 -34
  476. package/dist/index.js +211 -11351
  477. package/dist/ios/chrome.js +36 -0
  478. package/dist/ios/index.js +19 -315
  479. package/dist/ios/liquid-glass.js +103 -0
  480. package/dist/ios/native-app.js +46 -0
  481. package/dist/ios/navigation.js +63 -0
  482. package/dist/ios/phone-frame.js +64 -0
  483. package/dist/ios/preview-theme.js +83 -0
  484. package/dist/ios/preview.js +10 -137
  485. package/dist/ios/sheet.js +85 -0
  486. package/dist/ios/theme.js +19 -0
  487. package/dist/layout/AspectBox.js +27 -0
  488. package/dist/layout/Bleed.js +15 -0
  489. package/dist/layout/Center.js +21 -0
  490. package/dist/layout/Columns.js +70 -0
  491. package/dist/layout/Grid.js +50 -0
  492. package/dist/layout/GridOverlay.js +41 -0
  493. package/dist/layout/Page.js +11 -0
  494. package/dist/layout/Region.js +39 -0
  495. package/dist/layout/Row.js +45 -0
  496. package/dist/layout/SafeArea.js +26 -0
  497. package/dist/layout/Spacer.js +9 -0
  498. package/dist/layout/SplitPane.js +55 -0
  499. package/dist/layout/Stack.js +42 -0
  500. package/dist/layout/_responsive.js +16 -0
  501. package/dist/layout/_safeArea.js +18 -0
  502. package/dist/layout/index.d.ts +30 -8
  503. package/dist/layout/index.js +14 -2
  504. package/dist/lib/anchored-scroll.js +7 -0
  505. package/dist/lib/bleed.js +7 -0
  506. package/dist/lib/chart/cartesian.js +365 -0
  507. package/dist/lib/chart/chart-frame.js +163 -0
  508. package/dist/lib/chart/chart-legend.js +13 -0
  509. package/dist/lib/chart/chart-tooltip.js +38 -0
  510. package/dist/lib/chart/format.js +24 -0
  511. package/dist/lib/chart/geometry.js +114 -0
  512. package/dist/lib/chart/motion.js +34 -0
  513. package/dist/lib/chart/scale.js +72 -0
  514. package/dist/lib/chart/types.js +45 -0
  515. package/dist/lib/chart/use-arrival.js +41 -0
  516. package/dist/{chunk-SRQWQUTW.js → lib/cn.js} +5 -5
  517. package/dist/lib/component-guidance.js +215 -0
  518. package/dist/lib/density.js +54 -0
  519. package/dist/lib/field-shell.js +11 -0
  520. package/dist/lib/glyphs.js +9 -0
  521. package/dist/lib/heading-row.js +36 -0
  522. package/dist/lib/highlight-glide.js +84 -0
  523. package/dist/lib/hit-target.js +5 -0
  524. package/dist/lib/meta.js +0 -0
  525. package/dist/lib/portal-theme-scope.js +74 -0
  526. package/dist/{chunk-FKZXAFIY.js → lib/presentation.js} +8 -3
  527. package/dist/lib/refs.js +14 -0
  528. package/dist/lib/scrub-gesture.js +50 -0
  529. package/dist/lib/segmented.js +13 -0
  530. package/dist/lib/selection-card.js +41 -0
  531. package/dist/lib/status-tone.js +13 -0
  532. package/dist/lib/taxonomy.js +214 -0
  533. package/dist/lib/trim-label.js +20 -0
  534. package/dist/lib/use-copy.js +27 -0
  535. package/dist/lib/use-edge-fade.js +32 -0
  536. package/dist/lib/use-isomorphic-layout-effect.js +8 -0
  537. package/dist/lib/use-resize-observer.js +18 -0
  538. package/dist/lib/use-scroll-activity.js +52 -0
  539. package/dist/lib/use-wheel-to-horizontal.js +65 -0
  540. package/dist/motion/MotionProvider.js +75 -0
  541. package/dist/motion/Presence.js +112 -0
  542. package/dist/motion/Reveal.js +42 -0
  543. package/dist/motion/TextReveal.js +61 -0
  544. package/dist/motion/index.js +24 -0
  545. package/dist/motion/useInView.js +36 -0
  546. package/dist/motion/useReducedMotion.js +25 -0
  547. package/dist/motion/viewTransition.js +47 -0
  548. package/dist/patterns/auth-page.js +90 -0
  549. package/dist/patterns/auth-page.meta.js +27 -0
  550. package/dist/patterns/brand-landing-page.js +341 -0
  551. package/dist/patterns/brand-landing-page.meta.js +28 -0
  552. package/dist/patterns/chat-panel.js +185 -0
  553. package/dist/patterns/chat-panel.meta.js +31 -0
  554. package/dist/patterns/dashboard-page.js +332 -0
  555. package/dist/patterns/dashboard-page.meta.js +29 -0
  556. package/dist/patterns/feed-page.js +205 -0
  557. package/dist/patterns/feed-page.meta.js +25 -0
  558. package/dist/patterns/index.js +34 -2140
  559. package/dist/patterns/landing-page.js +356 -0
  560. package/dist/patterns/landing-page.meta.js +26 -0
  561. package/dist/patterns/list-detail-page.js +373 -0
  562. package/dist/patterns/list-detail-page.meta.js +24 -0
  563. package/dist/patterns/settings-page.js +268 -0
  564. package/dist/patterns/settings-page.meta.js +27 -0
  565. package/dist/presets/index.d.ts +7 -1
  566. package/dist/presets/index.js +53 -185
  567. package/dist/styles.css +118 -3
  568. package/dist/{theme-provider-kQszHYji.d.ts → theme-provider-S0NVJs2Y.d.ts} +6 -1
  569. package/dist/{chunk-NETUFI34.js → themes/_contract.js} +14 -2
  570. package/dist/themes/atelier/index.js +17 -1
  571. package/dist/themes/contrast.js +288 -0
  572. package/dist/themes/ember/ember.css +6 -6
  573. package/dist/themes/ember/index.js +16 -1
  574. package/dist/themes/index.d.ts +1 -1
  575. package/dist/themes/index.js +3 -8
  576. package/dist/themes/meridian/index.js +16 -1
  577. package/dist/themes/poppy/index.js +17 -1
  578. package/dist/themes/prism/index.js +17 -1
  579. package/dist/themes/prism/prism.css +2 -2
  580. package/dist/themes/registry.js +59 -0
  581. package/dist/themes/sable/index.js +16 -1
  582. package/dist/{chunk-EFLC4NA3.js → themes/theme-provider.js} +38 -54
  583. package/dist/themes/verdant/index.js +17 -1
  584. package/llms/blocks/media-player-block.md +20 -7
  585. package/llms/blocks/product-card-block.md +6 -4
  586. package/llms/blocks/task-list-block.md +1 -1
  587. package/llms/blocks/traffic-sources-block.md +1 -1
  588. package/llms/blocks/traffic-trend-block.md +5 -3
  589. package/llms/blocks/verify-code-block.md +1 -1
  590. package/llms/components/app-shell.md +5 -2
  591. package/llms/components/avatar.md +25 -3
  592. package/llms/components/button.md +20 -1
  593. package/llms/components/card.md +1 -1
  594. package/llms/components/disclosure-picker.md +168 -0
  595. package/llms/components/font-picker.md +104 -0
  596. package/llms/components/media-image.md +98 -0
  597. package/llms/components/media-tile.md +143 -0
  598. package/llms/components/page-header.md +6 -5
  599. package/llms/components/pie-chart.md +2 -2
  600. package/llms/components/radial-chart.md +1 -1
  601. package/llms/components/section-header.md +144 -0
  602. package/llms/components/sheet.md +10 -0
  603. package/llms/components/sidebar.md +4 -3
  604. package/llms/components/slider.md +14 -2
  605. package/llms/components/theme-selector.md +44 -4
  606. package/llms/components/tree.md +3 -3
  607. package/llms/layout/grid.md +24 -3
  608. package/llms/layout/region.md +16 -3
  609. package/llms/patterns/dashboard-page.md +3 -6
  610. package/llms/patterns/feed-page.md +4 -4
  611. package/llms/patterns/list-detail-page.md +1 -0
  612. package/llms/patterns/settings-page.md +2 -5
  613. package/llms/reference/utilities.md +84 -4
  614. package/llms/tasks/building-a-page.md +34 -3
  615. package/llms/tasks/converting-an-app.md +14 -4
  616. package/llms/tasks/creating-a-preset.md +4 -1
  617. package/llms/tasks/reviewing-a-design.md +19 -0
  618. package/llms/tasks/using-presets.md +19 -0
  619. package/llms-full.txt +1066 -78
  620. package/llms.txt +26 -9
  621. package/package.json +1 -1
  622. package/registry.json +2342 -164
  623. package/scripts/agent-setup.mjs +90 -0
  624. package/scripts/design-presets.mjs +59 -6
  625. package/scripts/grade.mjs +195 -29
  626. package/scripts/lint-rules.mjs +486 -10
  627. package/scripts/review-evidence.mjs +28 -1
  628. package/skills/sable/SKILL.md +67 -24
  629. package/targets/ios.json +45 -0
  630. package/dist/chunk-3IWZYBGH.js +0 -398
  631. package/dist/chunk-7MG4SHYX.js +0 -15
  632. package/dist/chunk-7QSDYBXO.js +0 -7421
  633. package/dist/chunk-ADLLDWLW.js +0 -15
  634. package/dist/chunk-C4Z3CF54.js +0 -15
  635. package/dist/chunk-EDXLM7YL.js +0 -5078
  636. package/dist/chunk-EHH4MHO6.js +0 -14
  637. package/dist/chunk-K7RUHUGC.js +0 -14
  638. package/dist/chunk-KO3NAEBF.js +0 -15
  639. package/dist/chunk-L4IEIQQA.js +0 -14
  640. package/dist/chunk-QJ6FWTUG.js +0 -40
  641. package/dist/chunk-VVW4UCDI.js +0 -4908
  642. package/dist/chunk-XAZGMY73.js +0 -187
@@ -65,7 +65,7 @@ function* openingTags(text, tagPattern) {
65
65
  else if (ch === ">" && depth === 0) break
66
66
  }
67
67
  if (i >= text.length) continue // unterminated — malformed source, not our problem
68
- yield { tag: m[1], attrs: text.slice(m.index + m[0].length, i), index: m.index }
68
+ yield { tag: m[1], attrs: text.slice(m.index + m[0].length, i), index: m.index, end: i }
69
69
  }
70
70
  }
71
71
 
@@ -95,7 +95,12 @@ function classNameOf(attrs) {
95
95
  return ""
96
96
  }
97
97
 
98
- /** Every class token in a className expression, with its variant prefixes (`hover:`, `md:`) split off. */
98
+ /**
99
+ * Every class token in a className expression, with its variant prefixes (`hover:`, `md:`) split off.
100
+ * `variants` are the top-level prefixes only: `[@media(hover:none)]:opacity-100`
101
+ * has the ONE variant `[@media(hover:none)]` — the `hover:` inside the brackets
102
+ * is a media feature, not a variant.
103
+ */
99
104
  function classTokens(expr) {
100
105
  const out = []
101
106
  for (const s of expr.matchAll(/(["'`])((?:(?!\1).)*)\1/gs)) {
@@ -103,15 +108,286 @@ function classTokens(expr) {
103
108
  if (!raw || raw.includes("${")) continue
104
109
  // The base utility is what follows the last `:` outside brackets.
105
110
  let depth = 0
106
- let cut = 0
111
+ let from = 0
112
+ const variants = []
107
113
  for (let k = 0; k < raw.length; k++) {
108
114
  if (raw[k] === "[" || raw[k] === "(") depth++
109
115
  else if (raw[k] === "]" || raw[k] === ")") depth--
110
- else if (raw[k] === ":" && depth === 0) cut = k + 1
116
+ else if (raw[k] === ":" && depth === 0) {
117
+ variants.push(raw.slice(from, k))
118
+ from = k + 1
119
+ }
120
+ }
121
+ out.push({ raw, base: raw.slice(from).replace(/^!/, ""), variants })
122
+ }
123
+ }
124
+ return out
125
+ }
126
+
127
+ /** A variant that styles a pointer state: hover, active, focus-visible — and their group-/peer- (optionally /named) forms. */
128
+ const POINTER_STATE_VARIANT = /^(?:(?:group|peer)-)?(?:hover|active|focus-visible)(?:\/[\w-]+)?$/
129
+
130
+ /** Utilities that set a property no transition can interpolate: display, visibility, text-decoration-line. */
131
+ const DISCRETE_UTILITY = /^(?:hidden|block|inline|inline-block|flex|inline-flex|grid|inline-grid|contents|table|invisible|visible|collapse|underline|overline|line-through|no-underline)$/
132
+
133
+ /* ── Assembly lookalikes: the shared reading of an element and its content. ────
134
+ * lookalike-control sees a control by its clothes. The four lookalike-* rules
135
+ * below see the bigger pieces an app assembles from Sable's utilities and tokens
136
+ * — a card grid, a link tile, an avatar chip, a cover frame — which every token
137
+ * rule passes, because every class on them is legal. An e2e build shipped all
138
+ * four through lint, a 100/100 grade and a visual review; a source audit found
139
+ * them. The helpers here are what those rules and grade's surface inventory read. */
140
+
141
+ /** Breakpoint variants that Grid's `cols` map takes (Tailwind's defaults). */
142
+ const BREAKPOINT_VARIANT = /^(?:sm|md|lg|xl|2xl)$/
143
+
144
+ /** Children that make a grid a form layout, not a collection: fields and their parts. */
145
+ const FORM_PART = /^(?:input|select|textarea|label|fieldset|legend|button|option)$|Field|Input|Label|Select|Textarea|Checkbox|Switch|Radio|Combobox|Slider|Button|Fieldset|Form/
146
+
147
+ /** A corner radius that makes a box a surface: any step but none or full. */
148
+ const SURFACE_RADIUS = /^rounded(?:-(?:xs|sm|md|lg|xl|[2-4]xl|\[[^\]]+\]|\([^)]+\)|concentric[\w-]*))?$/
149
+
150
+ /** A fixed square-ish measure: `size-12`, `size-[52px]`, `size-(--x)`. Percentages and keywords are not fixed. */
151
+ const FIXED_MEASURE = /^(?:\d+(?:\.\d+)?|\[[^\]%]+\]|\([^)%]+\))$/
152
+
153
+ /** Media an avatar or a cover frame holds. */
154
+ const MEDIA_CHILD = /<(?:img|picture|video|GradientArt|Image)\b/
155
+
156
+ /**
157
+ * The text with the literal part of every template literal that holds MARKUP
158
+ * blanked (length and newlines kept, so offsets and line numbers hold). A docs
159
+ * page carries JSX as data — a code sample in a backtick string — and a lookalike
160
+ * rule must not report the sample. A className template (`grid ${cols}`) carries
161
+ * no `<tag`, so it is kept and its classes still count.
162
+ */
163
+ function maskMarkupTemplates(text) {
164
+ const out = text.split("")
165
+ const stack = [] // { from, segs } = inside a template literal; number = brace depth of a ${ } inside one
166
+ let quote = null
167
+ const close = (t) => {
168
+ if (!t.segs.some(([a, b]) => /<[A-Za-z]/.test(text.slice(a, b)))) return
169
+ for (const [a, b] of t.segs) for (let k = a; k < b; k++) if (out[k] !== "\n") out[k] = " "
170
+ }
171
+ for (let i = 0; i < text.length; i++) {
172
+ const c = text[i]
173
+ const top = stack[stack.length - 1]
174
+ if (top && typeof top === "object") {
175
+ if (c === "\\") i++
176
+ else if (c === "`") {
177
+ top.segs.push([top.from, i])
178
+ stack.pop()
179
+ close(top)
180
+ } else if (c === "$" && text[i + 1] === "{") {
181
+ top.segs.push([top.from, i])
182
+ stack.push(0)
183
+ i++
184
+ }
185
+ continue
186
+ }
187
+ if (quote) {
188
+ // JSX text is full of apostrophes; a quote never spans a line in code.
189
+ if (c === "\\") i++
190
+ else if (c === quote || c === "\n") quote = null
191
+ continue
192
+ }
193
+ if (c === "'" || c === '"') quote = c
194
+ else if (c === "`") stack.push({ from: i + 1, segs: [] })
195
+ else if (typeof top === "number") {
196
+ if (c === "{") stack[stack.length - 1]++
197
+ else if (c === "}") {
198
+ if (top === 0) {
199
+ stack.pop()
200
+ stack[stack.length - 1].from = i + 1
201
+ } else stack[stack.length - 1]--
111
202
  }
112
- out.push({ raw, base: raw.slice(cut).replace(/^!/, "") })
113
203
  }
114
204
  }
205
+ return out.join("")
206
+ }
207
+
208
+ /** Index of the `}` closing the `{` at `i`. Strings are skipped; a quote ends at a line break (JSX text). */
209
+ function skipBraces(text, i) {
210
+ let depth = 0
211
+ let quote = null
212
+ for (; i < text.length; i++) {
213
+ const c = text[i]
214
+ if (quote) {
215
+ if (c === "\\") i++
216
+ else if (c === quote || (c === "\n" && quote !== "`")) quote = null
217
+ continue
218
+ }
219
+ if (c === '"' || c === "'" || c === "`") quote = c
220
+ else if (c === "{") depth++
221
+ else if (c === "}" && --depth === 0) return i
222
+ }
223
+ return text.length
224
+ }
225
+
226
+ /** Index of the `>` ending the tag that opens at `i`, as openingTags finds it. */
227
+ function tagEnd(text, i) {
228
+ let depth = 0
229
+ let quote = null
230
+ for (i++; i < text.length; i++) {
231
+ const c = text[i]
232
+ if (quote) {
233
+ if (c === "\\") i++
234
+ else if (c === quote) quote = null
235
+ continue
236
+ }
237
+ if (c === '"' || c === "'" || c === "`") quote = c
238
+ else if (c === "{") depth++
239
+ else if (c === "}") depth--
240
+ else if (c === ">" && depth === 0) return i
241
+ }
242
+ return text.length
243
+ }
244
+
245
+ /**
246
+ * What an element directly holds: the tag names of its child elements, its
247
+ * child `{…}` expressions, its loose text, and its whole inner source. Lexical,
248
+ * like everything here: it pairs `<x>` with `</x>` by depth and skips `{…}`
249
+ * whole, so a comparison inside an expression cannot open a tag.
250
+ */
251
+ function elementContent(text, open) {
252
+ const out = { tags: [], exprs: [], text: "", inner: "" }
253
+ if (/\/\s*$/.test(open.attrs)) return out // self-closing
254
+ const start = open.end + 1
255
+ const limit = Math.min(text.length, start + 40000)
256
+ let depth = 0
257
+ let i = start
258
+ while (i < limit) {
259
+ const c = text[i]
260
+ if (c === "{") {
261
+ const j = skipBraces(text, i)
262
+ if (depth === 0) out.exprs.push(text.slice(i + 1, j))
263
+ i = j + 1
264
+ continue
265
+ }
266
+ if (c === "<" && text[i + 1] === "/") {
267
+ if (depth === 0) break
268
+ depth--
269
+ const j = text.indexOf(">", i)
270
+ i = j === -1 ? limit : j + 1
271
+ continue
272
+ }
273
+ if (c === "<" && /[A-Za-z>]/.test(text[i + 1] ?? "")) {
274
+ const name = /^<([A-Za-z][\w.]*)?/.exec(text.slice(i, i + 80))?.[1]
275
+ const j = tagEnd(text, i)
276
+ if (depth === 0 && name) out.tags.push(name)
277
+ if (text[j - 1] !== "/") depth++
278
+ i = j + 1
279
+ continue
280
+ }
281
+ if (depth === 0) out.text += c
282
+ i++
283
+ }
284
+ out.inner = text.slice(start, i)
285
+ return out
286
+ }
287
+
288
+ /**
289
+ * Is the tag at `index` the element handed to a component's `render` prop?
290
+ * `<Card render={<a href="/x" className="…" />}>` already renders the Sable
291
+ * component; its element's classes are not an app-built surface.
292
+ */
293
+ function insideRender(text, index) {
294
+ // The nearest `render={` before the tag whose brace is still open at it —
295
+ // which also covers a render function, `render={({ className, ...props }) => <a …`.
296
+ const before = text.slice(Math.max(0, index - 600), index)
297
+ const opens = [...before.matchAll(/\brender\s*=\s*\{/g)]
298
+ const last = opens[opens.length - 1]
299
+ if (!last) return false
300
+ let depth = 0
301
+ for (let k = last.index + last[0].length - 1; k < before.length; k++) {
302
+ if (before[k] === "{") depth++
303
+ else if (before[k] === "}") depth--
304
+ }
305
+ return depth > 0
306
+ }
307
+
308
+ /** Unprefixed class bases (no `md:`, `hover:`…) of an element, plus every token. */
309
+ function classesOf(attrs) {
310
+ const tokens = classTokens(classNameOf(attrs))
311
+ const plain = tokens.filter((t) => !t.variants.length).map((t) => t.base)
312
+ return { tokens, plain, has: (re) => plain.some((b) => re.test(b)), find: (re) => plain.find((b) => re.test(b)) }
313
+ }
314
+
315
+ /** A fixed size on a round box: `size-12`, or equal `h-*` and `w-*`. Returns the measure or null. */
316
+ function fixedSize(c) {
317
+ const size = c.find(/^size-/)?.slice(5)
318
+ if (size && FIXED_MEASURE.test(size)) return size
319
+ const h = c.find(/^h-/)?.slice(2)
320
+ const w = c.find(/^w-/)?.slice(2)
321
+ return h && h === w && FIXED_MEASURE.test(h) ? h : null
322
+ }
323
+
324
+ /** Padding big enough for a card: p-3 and up (or py-3 and up), or an arbitrary/var padding. */
325
+ function cardPadding(c) {
326
+ return c.plain.some((b) => {
327
+ const m = /^p[y]?-(?:(\d+(?:\.\d+)?)|\[[^\]]+\]|\([^)]+\))$/.exec(b)
328
+ return m && (m[1] == null || Number(m[1]) >= 3)
329
+ })
330
+ }
331
+
332
+ /** Grid's `gap` prop for a Tailwind `gap-N` (rhythm units are 2 spacing steps). */
333
+ function gridGap(c) {
334
+ const n = /^gap-(\d+(?:\.\d+)?)$/.exec(c.find(/^gap-\d/) ?? "")?.[1]
335
+ return n == null ? "" : ` gap={${Number(n) / 2}}`
336
+ }
337
+
338
+ /** `aspect-*` → AspectBox's ratio, as source. */
339
+ function aspectRatio(base) {
340
+ if (base === "aspect-square") return "1"
341
+ if (base === "aspect-video") return "16 / 9"
342
+ const m = /^aspect-(?:\[(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)\]|(\d+)\/(\d+))$/.exec(base)
343
+ return m ? `${m[1] ?? m[3]} / ${m[2] ?? m[4]}` : "…"
344
+ }
345
+
346
+ /**
347
+ * The app-owned surfaces in one file: intrinsic elements wearing a surface's
348
+ * or a shape's classes. Not failures — a review list, so an agent's reuse
349
+ * inventory starts from what the source actually holds rather than from memory.
350
+ * `sable grade` collects it and `sable check` prints it.
351
+ *
352
+ * surface bg-card / bg-popover, or border + radius + padding → Card (or the component that owns it)
353
+ * media frame aspect-* + overflow-hidden → AspectBox
354
+ * round rounded-full at a fixed size → Avatar, or StatusDot for a dot
355
+ */
356
+ export function surfaceInventory(text) {
357
+ const src = maskMarkupTemplates(stripComments(text))
358
+ const out = []
359
+ for (const m of openingTags(src, /^[a-z][a-z0-9]*$/)) {
360
+ if (insideRender(src, m.index)) continue
361
+ const c = classesOf(m.attrs)
362
+ if (!c.plain.length) continue
363
+ let kind = null
364
+ let consider = null
365
+ const size = c.has(/^rounded-full$/) ? fixedSize(c) : null
366
+ if (size) {
367
+ kind = "round"
368
+ const holds = m.tag === "img" || (() => {
369
+ const content = elementContent(src, m)
370
+ return MEDIA_CHILD.test(content.inner) || /^[A-Z]{1,3}$/.test(content.text.trim())
371
+ })()
372
+ consider = holds
373
+ ? "Avatar"
374
+ : /^(?:0?\.5|1|1\.5|2|2\.5|3|px)$/.test(size)
375
+ ? "StatusDot, if it reports a status"
376
+ : "Avatar if it shows a person; otherwise app-owned (a handle, a swatch)"
377
+ } else if (c.has(/^aspect-(?!auto$)/) && c.has(/^overflow-(?:hidden|clip)$/)) {
378
+ kind = "media frame"
379
+ consider = "AspectBox"
380
+ } else if (
381
+ c.has(/^bg-(?:card|popover)(?:\/|$)/) ||
382
+ (c.has(/^border(?:-\d+)?$/) && c.has(SURFACE_RADIUS) && c.has(/^p[xy]?-/))
383
+ ) {
384
+ kind = "surface"
385
+ consider = m.tag === "a" || m.tag === "button" ? `Card render={<${m.tag} />}` : "Card, or the component that owns this surface"
386
+ }
387
+ if (!kind) continue
388
+ const classes = c.plain.join(" ")
389
+ out.push({ index: m.index, tag: m.tag, kind, classes: classes.length > 70 ? `${classes.slice(0, 67)}…` : classes, consider })
390
+ }
115
391
  return out
116
392
  }
117
393
 
@@ -370,15 +646,41 @@ export function makeRules({ semanticTokens, motionTokens, labelContrast = null,
370
646
  // up with two transitions fighting over the same property.
371
647
  for (const m of openingTags(text, /^[a-z][a-z0-9]*$/)) {
372
648
  const attrs = m.attrs
373
- if (!/\bclassName\s*=/.test(attrs)) continue
374
- if (!/\b(?:hover|active|focus-visible):/.test(attrs)) continue
649
+ // Judged on the parsed variants, not a regex over the text: a
650
+ // capability query like `[@media(hover:none)]:opacity-100` has
651
+ // "hover:" in it and no pointer state at all.
652
+ const states = classTokens(classNameOf(attrs)).filter(({ variants }) =>
653
+ variants.some((v) => POINTER_STATE_VARIANT.test(v)),
654
+ )
655
+ const pointer = states[0]
656
+ if (!pointer) continue
375
657
  if (/\bmotion-[a-z-]+/.test(attrs)) continue
658
+ // `reveal-on-hover` declares motion-interactive's own transition
659
+ // list, so for this rule it is a recipe.
660
+ if (/\breveal-on-hover\b/.test(attrs)) continue
376
661
  // `transition-colors` is already caught by its own rule and its fix is
377
662
  // the same recipe; reporting both on one element is noise.
378
663
  if (/\btransition-colors\b/.test(attrs)) continue
379
664
  out.push({
380
665
  index: m.index,
381
- match: `<${m.tag} …${/\b(?:hover|active|focus-visible):[\w[\]/.-]+/.exec(attrs)?.[0] ?? ""}`,
666
+ match: `<${m.tag} …${pointer.raw}`,
667
+ // `motion-interactive` cannot help a state that only flips display
668
+ // or an underline: there is nothing to interpolate. Point at the
669
+ // fix that can.
670
+ ...(states.every(({ base }) => DISCRETE_UTILITY.test(base)) && {
671
+ why:
672
+ "An element whose pointer state only flips a discrete property (display, visibility, an underline), which no transition can animate, so `motion-interactive` alone will not help. " +
673
+ "To reveal an action on hover, keep it rendered and put `reveal-on-hover` on it with `group` on its row or tile: it fades with opacity, shows on focus, and stays visible on touch, where a hand-written `opacity-0 group-hover:opacity-100` hides it for good. To cross-fade two icons, `motion-icon-swap`. " +
674
+ "If an instant flip is the intent, disable the rule on that line with the reason.",
675
+ }),
676
+ // A group-hover opacity swap is a hover REVEAL, and adding
677
+ // motion-interactive to it would still leave the action invisible on
678
+ // every touch device. Point at the utility that is right on both.
679
+ ...(states.some(({ base, variants }) => /^opacity-/.test(base) && variants.some((v) => /^group-hover\b/.test(v))) && {
680
+ why:
681
+ "A hover reveal (`group-hover:opacity-*`) with no motion — and, as written, an action no touch device can ever see, because touch has no hover. " +
682
+ "Use `reveal-on-hover` on the action with `group` on its row or tile: it is hidden only where `(hover: hover)`, shows on focus-within, keeps an open menu's trigger visible, and fades on the motion tokens.",
683
+ }),
382
684
  })
383
685
  }
384
686
  return out
@@ -528,6 +830,180 @@ export function makeRules({ semanticTokens, motionTokens, labelContrast = null,
528
830
  return out
529
831
  },
530
832
  },
833
+ /* ── Assembly lookalikes. The siblings of lookalike-control for the pieces
834
+ * bigger than a control: a card grid, a link tile, an avatar, a cover frame.
835
+ * One id each, so an exception is disabled for the one shape it covers.
836
+ * CONSUMER-scoped: the package's own components are where these are built.
837
+ * Each one demands a signal beyond the classes that a single legitimate use
838
+ * would not carry — mapped items, a fill AND padding, content in the circle,
839
+ * media in the frame — because a lookalike rule that fires on a form layout
840
+ * or a text link is a rule people learn to disable. */
841
+ {
842
+ id: "lookalike-grid",
843
+ scope: "consumer",
844
+ since: "0.7.0",
845
+ file: true,
846
+ code: true,
847
+ why:
848
+ "A raw CSS grid laid over a collection — `grid` with an auto-fill/auto-fit `minmax()` track, or uniform `grid-cols-N` columns over mapped or repeated items. That is Sable's `Grid`: `cols` takes the same counts per breakpoint (`{ base: 2, md: 4 }`), `minItemWidth` is the auto-fit track with a `min(…, 100%)` guard so a cell never overflows a narrow container (a bare `minmax(12rem,1fr)` does), every track is `minmax(0, 1fr)` so one long string cannot blow a column out, and the gap defaults to the theme's `--grid-gutter` or takes rhythm units (`gap={2}` = `gap-4`). " +
849
+ "Not flagged: a grid with no columns, a form layout (field children, no mapping), a non-uniform track (`grid-cols-[auto_1fr]`), a `ul`/`ol`/`dl` (Grid renders a div), or an element with a widget role.",
850
+ scan(text) {
851
+ const src = maskMarkupTemplates(text)
852
+ const out = []
853
+ for (const m of openingTags(src, /^div$/)) {
854
+ if (insideRender(src, m.index)) continue
855
+ // A widget or a graphic (role="img", "grid", "radiogroup") owns its own
856
+ // semantics; a list role is still a collection.
857
+ if (/\brole\s*=/.test(m.attrs) && !/\brole\s*=\s*\{?\s*["'`]list["'`]/.test(m.attrs)) continue
858
+ const c = classesOf(m.attrs)
859
+ if (!c.has(/^grid$/)) continue
860
+ const tracks = c.tokens.filter((t) => /^grid-cols-/.test(t.base))
861
+ if (!tracks.length) continue
862
+ const auto = tracks
863
+ .map((t) => /^grid-cols-\[repeat\(auto-(?:fill|fit),\s*minmax\((min\([^)]*\)|[^,]+),/.exec(t.base))
864
+ .find(Boolean)
865
+ if (auto) {
866
+ const min = auto[1].replace(/_/g, " ").replace(/^min\(\s*([^,]+),.*$/, "$1").trim()
867
+ out.push({ index: m.index, match: `raw auto-fill grid → <Grid minItemWidth="${min}"${gridGap(c)}>` })
868
+ continue
869
+ }
870
+ // Uniform column counts per breakpoint, or nothing: a track Grid cannot
871
+ // express (`[auto_1fr]`, `subgrid`, a container-query variant) is
872
+ // a layout, not a collection grid.
873
+ const cols = {}
874
+ let uniform = true
875
+ for (const t of tracks) {
876
+ const bp = t.variants.length === 0 ? "base" : t.variants.length === 1 && BREAKPOINT_VARIANT.test(t.variants[0]) ? t.variants[0] : null
877
+ const n = /^grid-cols-(?:(\d+)|\[repeat\((\d+),\s*minmax\(0,\s*1fr\)\)\])$/.exec(t.base)
878
+ if (!bp || !n) {
879
+ uniform = false
880
+ break
881
+ }
882
+ cols[bp] = Number(n[1] ?? n[2])
883
+ }
884
+ if (!uniform) continue
885
+ if (Math.max(...Object.values(cols)) < 2) continue
886
+ const content = elementContent(src, m)
887
+ // Children placed by span are an explicit layout (Columns + Col), not
888
+ // a collection of equal cells.
889
+ if (/\b(?:col|row)-(?:span|start|end)-/.test(content.inner)) continue
890
+ // The child expression itself maps (`{items.map(…)}`); a map nested in
891
+ // one child's markup (`{cond ? <Panel>{a.map(…)}</Panel> : null}`) is
892
+ // that child's list, not this grid's cells.
893
+ const mapped = content.exprs.some((e) => /\.(?:flat)?[mM]ap\s*\(|Array\.from\s*\(/.test(e.split("<")[0]))
894
+ // Three or more of the same component or item element. Bare wrappers
895
+ // (div, span, p) say nothing about a collection, and fields make a form.
896
+ const counts = new Map()
897
+ for (const tag of content.tags) counts.set(tag, (counts.get(tag) ?? 0) + 1)
898
+ const repeated = [...counts].some(([tag, n]) => n >= 3 && !/^(?:div|span|p)$/.test(tag) && !FORM_PART.test(tag))
899
+ if (!mapped && !repeated) continue
900
+ // `grid md:grid-cols-2` is one column until md, as Tailwind's implicit track is.
901
+ if (!("base" in cols)) cols.base = 1
902
+ const order = ["base", "sm", "md", "lg", "xl", "2xl"].filter((b) => b in cols)
903
+ const prop = order.length === 1 ? `{${cols.base}}` : `{{ ${order.map((b) => `${b === "2xl" ? '"2xl"' : b}: ${cols[b]}`).join(", ")} }}`
904
+ out.push({ index: m.index, match: `raw grid over ${mapped ? "mapped" : "repeated"} items → <Grid cols=${prop}${gridGap(c)}>` })
905
+ }
906
+ return out
907
+ },
908
+ },
909
+ {
910
+ id: "lookalike-card",
911
+ scope: "consumer",
912
+ since: "0.7.0",
913
+ file: true,
914
+ code: true,
915
+ why:
916
+ "A link or button dressed as a card — a radius, a surface (`bg-card` / `bg-popover`, or a border) and card padding on an <a>, <button> or router <Link>. That is `<Card render={<Link to=… />}>` (or `<a href>` / `<button>`): rendered as a link or button, Card adds the hover (a stronger edge and shadow), a full-card focus-visible ring and motion-interactive easing; its padding follows the density control (`--card-p`) and its corner follows the theme radius; children answer the hover with `group-hover/card:`. Hand-built, a tile keeps none of that when the theme or density moves. " +
917
+ "Not flagged: a text link, a nav row (no surface), a pill (`rounded-full`), a button fill (lookalike-control's), or an element inside a `render` prop.",
918
+ scan(text) {
919
+ const src = maskMarkupTemplates(text)
920
+ // A `Link` the file takes from Sable is Sable's.
921
+ const sableLinks = new Set(
922
+ [...src.matchAll(/import\s*\{([^}]*)\}\s*from\s*["']@sableui\/react[^"']*["']/g)].flatMap((m) =>
923
+ m[1].split(",").map((s) => s.trim().split(/\s+as\s+/).pop()),
924
+ ),
925
+ )
926
+ const out = []
927
+ for (const m of openingTags(src, /^(?:a|button|Link|NavLink)$/)) {
928
+ if (sableLinks.has(m.tag) || insideRender(src, m.index)) continue
929
+ const c = classesOf(m.attrs)
930
+ if (!c.has(SURFACE_RADIUS)) continue
931
+ const fill = c.find(/^bg-(?:card|popover)(?:\/|$)/) ?? c.find(/^sable-surface$/)
932
+ const edge = c.find(/^border(?:-\d+)?$/)
933
+ if (!fill && !edge) continue
934
+ if (!cardPadding(c)) continue
935
+ if (c.has(/^bg-(?:primary|secondary|destructive)$/)) continue
936
+ const el = m.tag === "a" ? "<a href=… />" : m.tag === "button" ? "<button />" : `<${m.tag} to=… />`
937
+ const radius = c.find(SURFACE_RADIUS)
938
+ out.push({ index: m.index, match: `<${m.tag}> dressed as a card (${[radius, edge, fill].filter(Boolean).join(" ")}) → <Card render={${el}}>` })
939
+ }
940
+ return out
941
+ },
942
+ },
943
+ {
944
+ id: "lookalike-avatar",
945
+ scope: "consumer",
946
+ since: "0.7.0",
947
+ file: true,
948
+ code: true,
949
+ why:
950
+ "A round, fixed-size box holding a person's or artist's picture — `rounded-full` + `size-*` (or equal `h-*`/`w-*`) around an <img>, GradientArt or initials, or a round fixed-size <img> itself. That is `Avatar`: `src`/`alt` with a fallback that shows while the image loads or when it fails, `variant=\"art\"` for initials over GradientArt seeded by the person (a stable colour each, from the theme), a `size` ladder, an accessible name (role=img), a `status` slot that clears the circle, and AvatarGroup for an overlapping stack. " +
951
+ "Not flagged: a dot or an icon in a circle (no picture or initials), a round box without a fixed size, or AvatarRoot and its parts.",
952
+ scan(text) {
953
+ const src = maskMarkupTemplates(text)
954
+ const out = []
955
+ for (const m of openingTags(src, /^(?:div|span|figure|img)$/)) {
956
+ if (insideRender(src, m.index)) continue
957
+ const c = classesOf(m.attrs)
958
+ if (!c.has(/^rounded-full$/)) continue
959
+ const size = fixedSize(c)
960
+ if (!size) continue
961
+ let holds = null
962
+ if (m.tag === "img") holds = "an image"
963
+ else {
964
+ const content = elementContent(src, m)
965
+ if (/<GradientArt\b/.test(content.inner)) holds = "GradientArt"
966
+ else if (MEDIA_CHILD.test(content.inner)) holds = "an image"
967
+ else if (
968
+ /^[A-Z]{1,3}$/.test(content.text.trim()) ||
969
+ content.exprs.some((e) => /initial|\[0\]|charAt\(0\)|\.(?:slice|substring)\(0,\s*[12]\)/i.test(e))
970
+ )
971
+ holds = "initials"
972
+ }
973
+ if (!holds) continue
974
+ const use = holds === "an image" ? "<Avatar src alt fallback>" : '<Avatar variant="art" fallback="…">'
975
+ out.push({ index: m.index, match: `<${m.tag}> rounded-full size-${size} holding ${holds} → ${use}` })
976
+ }
977
+ return out
978
+ },
979
+ },
980
+ {
981
+ id: "lookalike-media-frame",
982
+ scope: "consumer",
983
+ since: "0.7.0",
984
+ file: true,
985
+ code: true,
986
+ why:
987
+ "A cover or media frame built by hand — an `aspect-*` ratio + `overflow-hidden` wrapped around an <img>, <video>, <picture> or GradientArt. That is `AspectBox`: `ratio` as a number reserves the box before the media arrives (no layout shift while a cover loads or a shader starts), it clips and positions what it holds (`relative w-full overflow-hidden`), and it is the one frame the media components share, so a change to how frames behave reaches every cover. Put the radius on it as a className. For a cover with its title beneath (an album, an episode, a product), `MediaTile` composes this frame with fallback art for a missing or broken image and an action revealed on hover. " +
988
+ "Not flagged: a ratio box with no media in it (a pad, a canvas well), a frame without overflow clipping, an <img> sized by its own aspect class, or a round fixed-size frame (lookalike-avatar's).",
989
+ scan(text) {
990
+ const src = maskMarkupTemplates(text)
991
+ const out = []
992
+ for (const m of openingTags(src, /^(?:div|span|figure|a|li)$/)) {
993
+ if (insideRender(src, m.index)) continue
994
+ const c = classesOf(m.attrs)
995
+ const aspect = c.find(/^aspect-(?!auto$)/)
996
+ if (!aspect || !c.has(/^overflow-(?:hidden|clip)$/)) continue
997
+ // A round, fixed-size frame is an avatar, reported by lookalike-avatar.
998
+ if (c.has(/^rounded-full$/) && fixedSize(c)) continue
999
+ const inner = elementContent(src, m).inner
1000
+ const media = /<GradientArt\b/.test(inner) ? "GradientArt" : MEDIA_CHILD.exec(inner)?.[0].slice(1)
1001
+ if (!media) continue
1002
+ out.push({ index: m.index, match: `<${m.tag}> ${aspect} overflow-hidden around <${media}> → <AspectBox ratio={${aspectRatio(aspect)}}>` })
1003
+ }
1004
+ return out
1005
+ },
1006
+ },
531
1007
  {
532
1008
  id: "label-contrast",
533
1009
  /* CONSUMER-scoped. An app retunes `--primary` and keeps the theme's
@@ -1060,7 +1536,7 @@ export function lintText(text, rules, ctx) {
1060
1536
  ? rule.find(subject, ctx)
1061
1537
  : [...subject.matchAll(rule.regex)].map((m) => ({ match: m[0] }))
1062
1538
  for (const h of hits) {
1063
- out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: rule.why })
1539
+ out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: h.why ?? rule.why })
1064
1540
  }
1065
1541
  }
1066
1542
  })
@@ -1091,7 +1567,7 @@ export function lintText(text, rules, ctx) {
1091
1567
  for (const h of rule.scan(rule.code ? strippedText : text, ctx)) {
1092
1568
  const i = h.line != null ? h.line - 1 : lineOf(h.index ?? 0)
1093
1569
  if (suppress[i]?.has(rule.id)) continue
1094
- out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: rule.why })
1570
+ out.push({ line: i + 1, ruleId: rule.id, match: h.match, why: h.why ?? rule.why })
1095
1571
  }
1096
1572
  }
1097
1573
  }
@@ -9,12 +9,39 @@ export const DIMENSIONS = [
9
9
  ]
10
10
  export const CHECKS = ["primary-flow", "keyboard", "overflow", "reduced-motion"]
11
11
 
12
+ /**
13
+ * One filled example of each artifact shape, so the required fields are in the
14
+ * template rather than discoverable only by submitting a review and reading the
15
+ * errors. Ids and paths say `example`, the hash is a placeholder that matches no
16
+ * file, and the validator rejects them until they are replaced with real
17
+ * captures — a copied template cannot pass on its examples.
18
+ */
19
+ export const EXAMPLE_ARTIFACTS = [
20
+ {
21
+ id: "example-wide",
22
+ kind: "screenshot",
23
+ path: "captures/example-wide.png",
24
+ sha256: "replace-with-review.artifactHashes-after-inspecting-the-file",
25
+ viewport: { width: 1440, height: 900 },
26
+ state: "dashboard, loaded",
27
+ colorMode: "light",
28
+ motion: "full",
29
+ },
30
+ {
31
+ id: "example-keyboard",
32
+ kind: "report",
33
+ path: "captures/example-keyboard.md",
34
+ sha256: "replace-with-review.artifactHashes-after-inspecting-the-file",
35
+ state: "checkout flow, keyboard only",
36
+ },
37
+ ]
38
+
12
39
  export function reviewTemplate() {
13
40
  return {
14
41
  schemaVersion: 1,
15
42
  build: { name: "", brief: "", revision: "", sourceHash: "" },
16
43
  reviewer: { name: "", method: "agent" },
17
- artifacts: [],
44
+ artifacts: EXAMPLE_ARTIFACTS.map((artifact) => structuredClone(artifact)),
18
45
  dimensions: DIMENSIONS.map((id) => ({ id, status: "untested", observation: "", evidence: [] })),
19
46
  checks: CHECKS.map((id) => ({ id, status: "untested", observation: "", evidence: [] })),
20
47
  iterations: [],