@ethlete/cli 3.0.0-next.21 → 3.0.0-next.23

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 (340) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/design/assets.d.ts +9 -0
  3. package/design/define-call.ts +20 -11
  4. package/design/define-part.ts +102 -0
  5. package/design/embed-page.ts +70 -0
  6. package/design/interaction-rows.ts +32 -0
  7. package/design/mark-part.ts +66 -0
  8. package/design/svg-asset.ts +36 -0
  9. package/design/systems/config.json +10 -0
  10. package/design/systems/ethlete-components/components/button/button.ts +18 -0
  11. package/design/web/env.d.ts +20 -0
  12. package/design/web/frame.ts +173 -41
  13. package/design/wireframe-kit.ts +386 -0
  14. package/package.json +4 -2
  15. package/playwright/failure-reporter.d.ts +2 -0
  16. package/playwright/failure-reporter.js +1 -0
  17. package/src/index.js.map +1 -1
  18. package/src/lib/api/api-command.js.map +1 -1
  19. package/src/lib/api/api-context.js.map +1 -1
  20. package/src/lib/api/api-state.js.map +1 -1
  21. package/src/lib/api/auth-hint.js.map +1 -1
  22. package/src/lib/api/clear.js.map +1 -1
  23. package/src/lib/api/clone.js.map +1 -1
  24. package/src/lib/api/compose.js.map +1 -1
  25. package/src/lib/api/definition.js.map +1 -1
  26. package/src/lib/api/doctor.js.map +1 -1
  27. package/src/lib/api/fingerprint.js.map +1 -1
  28. package/src/lib/api/git.js.map +1 -1
  29. package/src/lib/api/help.js.map +1 -1
  30. package/src/lib/api/index.js.map +1 -1
  31. package/src/lib/api/load-definitions.js.map +1 -1
  32. package/src/lib/api/lock.js.map +1 -1
  33. package/src/lib/api/parse-argv.js.map +1 -1
  34. package/src/lib/api/ports.js.map +1 -1
  35. package/src/lib/api/resolve-checkout.js.map +1 -1
  36. package/src/lib/api/run-locked.js.map +1 -1
  37. package/src/lib/api/run-ready.js.map +1 -1
  38. package/src/lib/api/run-snapshot.js.map +1 -1
  39. package/src/lib/api/run-status.js.map +1 -1
  40. package/src/lib/api/run.js.map +1 -1
  41. package/src/lib/api/seeds.js.map +1 -1
  42. package/src/lib/api/setup.js.map +1 -1
  43. package/src/lib/api/snapshot.js.map +1 -1
  44. package/src/lib/api/state.js.map +1 -1
  45. package/src/lib/api/suggest.js.map +1 -1
  46. package/src/lib/api/validate-definition.js.map +1 -1
  47. package/src/lib/auth/auth-command.js.map +1 -1
  48. package/src/lib/auth/composer-auth.js.map +1 -1
  49. package/src/lib/auth/composer-repositories.js.map +1 -1
  50. package/src/lib/auth/gitlab-token.js.map +1 -1
  51. package/src/lib/auth/index.js.map +1 -1
  52. package/src/lib/config/diagnose.js.map +1 -1
  53. package/src/lib/config/index.js.map +1 -1
  54. package/src/lib/config/local-config.js.map +1 -1
  55. package/src/lib/design/asset-shot.d.ts +26 -0
  56. package/src/lib/design/asset-shot.js +191 -0
  57. package/src/lib/design/asset-shot.js.map +1 -0
  58. package/src/lib/design/browser.js.map +1 -1
  59. package/src/lib/design/calls-tsconfig.d.ts +14 -1
  60. package/src/lib/design/calls-tsconfig.js +23 -18
  61. package/src/lib/design/calls-tsconfig.js.map +1 -1
  62. package/src/lib/design/calls.d.ts +28 -0
  63. package/src/lib/design/calls.js +58 -0
  64. package/src/lib/design/calls.js.map +1 -0
  65. package/src/lib/design/changed-parts.d.ts +51 -0
  66. package/src/lib/design/changed-parts.js +103 -0
  67. package/src/lib/design/changed-parts.js.map +1 -0
  68. package/src/lib/design/check.js +53 -54
  69. package/src/lib/design/check.js.map +1 -1
  70. package/src/lib/design/contact-sheet.d.ts +86 -0
  71. package/src/lib/design/contact-sheet.js +197 -0
  72. package/src/lib/design/contact-sheet.js.map +1 -0
  73. package/src/lib/design/design-command.js +167 -6
  74. package/src/lib/design/design-command.js.map +1 -1
  75. package/src/lib/design/force-interactions.d.ts +13 -0
  76. package/src/lib/design/force-interactions.js +75 -0
  77. package/src/lib/design/force-interactions.js.map +1 -0
  78. package/src/lib/design/inbox-command.d.ts +21 -0
  79. package/src/lib/design/inbox-command.js +156 -0
  80. package/src/lib/design/inbox-command.js.map +1 -0
  81. package/src/lib/design/index.js.map +1 -1
  82. package/src/lib/design/json-layout.d.ts +5 -0
  83. package/src/lib/design/json-layout.js +41 -0
  84. package/src/lib/design/json-layout.js.map +1 -0
  85. package/src/lib/design/legacy.d.ts +7 -0
  86. package/src/lib/design/legacy.js +23 -8
  87. package/src/lib/design/legacy.js.map +1 -1
  88. package/src/lib/design/merge-compare.d.ts +57 -0
  89. package/src/lib/design/merge-compare.js +80 -0
  90. package/src/lib/design/merge-compare.js.map +1 -0
  91. package/src/lib/design/missing-package.js.map +1 -1
  92. package/src/lib/design/new-call.js.map +1 -1
  93. package/src/lib/design/part-marks.d.ts +12 -0
  94. package/src/lib/design/part-marks.js +38 -0
  95. package/src/lib/design/part-marks.js.map +1 -0
  96. package/src/lib/design/paths.d.ts +35 -2
  97. package/src/lib/design/paths.js +62 -4
  98. package/src/lib/design/paths.js.map +1 -1
  99. package/src/lib/design/product-command.d.ts +38 -0
  100. package/src/lib/design/product-command.js +158 -0
  101. package/src/lib/design/product-command.js.map +1 -0
  102. package/src/lib/design/ready-shots.d.ts +13 -0
  103. package/src/lib/design/ready-shots.js +55 -0
  104. package/src/lib/design/ready-shots.js.map +1 -0
  105. package/src/lib/design/render-fields.js.map +1 -1
  106. package/src/lib/design/render-packages.js.map +1 -1
  107. package/src/lib/design/render-worker.js.map +1 -1
  108. package/src/lib/design/render.d.ts +11 -2
  109. package/src/lib/design/render.js +60 -15
  110. package/src/lib/design/render.js.map +1 -1
  111. package/src/lib/design/serve.d.ts +17 -0
  112. package/src/lib/design/serve.js +487 -103
  113. package/src/lib/design/serve.js.map +1 -1
  114. package/src/lib/design/shot-command.d.ts +100 -0
  115. package/src/lib/design/shot-command.js +453 -0
  116. package/src/lib/design/shot-command.js.map +1 -0
  117. package/src/lib/design/shot.d.ts +97 -0
  118. package/src/lib/design/shot.js +346 -0
  119. package/src/lib/design/shot.js.map +1 -0
  120. package/src/lib/design/sketch-at.d.ts +16 -0
  121. package/src/lib/design/sketch-at.js +34 -0
  122. package/src/lib/design/sketch-at.js.map +1 -0
  123. package/src/lib/design/sketch-command.d.ts +15 -0
  124. package/src/lib/design/sketch-command.js +618 -0
  125. package/src/lib/design/sketch-command.js.map +1 -0
  126. package/src/lib/design/ssr-shims.js.map +1 -1
  127. package/src/lib/design/stamp.js.map +1 -1
  128. package/src/lib/design/status.d.ts +2 -2
  129. package/src/lib/design/status.js +2 -2
  130. package/src/lib/design/status.js.map +1 -1
  131. package/src/lib/design/studio-api.d.ts +112 -0
  132. package/src/lib/design/studio-api.js +489 -0
  133. package/src/lib/design/studio-api.js.map +1 -0
  134. package/src/lib/design/system/accept-command.d.ts +7 -0
  135. package/src/lib/design/system/accept-command.js +55 -0
  136. package/src/lib/design/system/accept-command.js.map +1 -0
  137. package/src/lib/design/system/assets.d.ts +34 -0
  138. package/src/lib/design/system/assets.js +64 -0
  139. package/src/lib/design/system/assets.js.map +1 -0
  140. package/src/lib/design/system/base.d.ts +33 -0
  141. package/src/lib/design/system/base.js +49 -0
  142. package/src/lib/design/system/base.js.map +1 -0
  143. package/src/lib/design/system/check-system.d.ts +23 -0
  144. package/src/lib/design/system/check-system.js +151 -0
  145. package/src/lib/design/system/check-system.js.map +1 -0
  146. package/src/lib/design/system/colour.d.ts +2 -0
  147. package/src/lib/design/system/colour.js +9 -0
  148. package/src/lib/design/system/colour.js.map +1 -0
  149. package/src/lib/design/system/comments/comment.d.ts +196 -0
  150. package/src/lib/design/system/comments/comment.js +332 -0
  151. package/src/lib/design/system/comments/comment.js.map +1 -0
  152. package/src/lib/design/system/comments/complete.d.ts +15 -0
  153. package/src/lib/design/system/comments/complete.js +63 -0
  154. package/src/lib/design/system/comments/complete.js.map +1 -0
  155. package/src/lib/design/system/comments/inbox.d.ts +49 -0
  156. package/src/lib/design/system/comments/inbox.js +100 -0
  157. package/src/lib/design/system/comments/inbox.js.map +1 -0
  158. package/src/lib/design/system/css-source.d.ts +12 -0
  159. package/src/lib/design/system/css-source.js +39 -0
  160. package/src/lib/design/system/css-source.js.map +1 -0
  161. package/src/lib/design/system/flows/flow.d.ts +66 -0
  162. package/src/lib/design/system/flows/flow.js +157 -0
  163. package/src/lib/design/system/flows/flow.js.map +1 -0
  164. package/src/lib/design/system/imports.d.ts +70 -0
  165. package/src/lib/design/system/imports.js +159 -0
  166. package/src/lib/design/system/imports.js.map +1 -0
  167. package/src/lib/design/system/kit.d.ts +4 -0
  168. package/src/lib/design/system/kit.js +51 -0
  169. package/src/lib/design/system/kit.js.map +1 -0
  170. package/src/lib/design/system/layers.d.ts +9 -0
  171. package/src/lib/design/system/layers.js +16 -0
  172. package/src/lib/design/system/layers.js.map +1 -0
  173. package/src/lib/design/system/list-system.d.ts +104 -0
  174. package/src/lib/design/system/list-system.js +212 -0
  175. package/src/lib/design/system/list-system.js.map +1 -0
  176. package/src/lib/design/system/notes/note.d.ts +53 -0
  177. package/src/lib/design/system/notes/note.js +116 -0
  178. package/src/lib/design/system/notes/note.js.map +1 -0
  179. package/src/lib/design/system/part-command.d.ts +7 -0
  180. package/src/lib/design/system/part-command.js +134 -0
  181. package/src/lib/design/system/part-command.js.map +1 -0
  182. package/src/lib/design/system/problem.d.ts +5 -0
  183. package/src/lib/design/system/problem.js +3 -0
  184. package/src/lib/design/system/problem.js.map +1 -0
  185. package/src/lib/design/system/product.d.ts +18 -0
  186. package/src/lib/design/system/product.js +35 -0
  187. package/src/lib/design/system/product.js.map +1 -0
  188. package/src/lib/design/system/render-check.d.ts +45 -0
  189. package/src/lib/design/system/render-check.js +427 -0
  190. package/src/lib/design/system/render-check.js.map +1 -0
  191. package/src/lib/design/system/resolve-product.d.ts +32 -0
  192. package/src/lib/design/system/resolve-product.js +62 -0
  193. package/src/lib/design/system/resolve-product.js.map +1 -0
  194. package/src/lib/design/system/rules/component-copy.d.ts +7 -0
  195. package/src/lib/design/system/rules/component-copy.js +53 -0
  196. package/src/lib/design/system/rules/component-copy.js.map +1 -0
  197. package/src/lib/design/system/rules/flow.d.ts +8 -0
  198. package/src/lib/design/system/rules/flow.js +60 -0
  199. package/src/lib/design/system/rules/flow.js.map +1 -0
  200. package/src/lib/design/system/rules/hard-coded-colour.d.ts +7 -0
  201. package/src/lib/design/system/rules/hard-coded-colour.js +27 -0
  202. package/src/lib/design/system/rules/hard-coded-colour.js.map +1 -0
  203. package/src/lib/design/system/rules/interactions.d.ts +4 -0
  204. package/src/lib/design/system/rules/interactions.js +16 -0
  205. package/src/lib/design/system/rules/interactions.js.map +1 -0
  206. package/src/lib/design/system/rules/kit-outside-pages.d.ts +4 -0
  207. package/src/lib/design/system/rules/kit-outside-pages.js +14 -0
  208. package/src/lib/design/system/rules/kit-outside-pages.js.map +1 -0
  209. package/src/lib/design/system/rules/motion.d.ts +4 -0
  210. package/src/lib/design/system/rules/motion.js +13 -0
  211. package/src/lib/design/system/rules/motion.js.map +1 -0
  212. package/src/lib/design/system/rules/stale-comment.d.ts +8 -0
  213. package/src/lib/design/system/rules/stale-comment.js +51 -0
  214. package/src/lib/design/system/rules/stale-comment.js.map +1 -0
  215. package/src/lib/design/system/rules/stale-replace.d.ts +4 -0
  216. package/src/lib/design/system/rules/stale-replace.js +65 -0
  217. package/src/lib/design/system/rules/stale-replace.js.map +1 -0
  218. package/src/lib/design/system/rules/svg-asset.d.ts +6 -0
  219. package/src/lib/design/system/rules/svg-asset.js +30 -0
  220. package/src/lib/design/system/rules/svg-asset.js.map +1 -0
  221. package/src/lib/design/system/rules/token-selectors.d.ts +3 -0
  222. package/src/lib/design/system/rules/token-selectors.js +87 -0
  223. package/src/lib/design/system/rules/token-selectors.js.map +1 -0
  224. package/src/lib/design/system/rules/undefined-token.d.ts +8 -0
  225. package/src/lib/design/system/rules/undefined-token.js +103 -0
  226. package/src/lib/design/system/rules/undefined-token.js.map +1 -0
  227. package/src/lib/design/system/rules/unused-shared-export.d.ts +18 -0
  228. package/src/lib/design/system/rules/unused-shared-export.js +60 -0
  229. package/src/lib/design/system/rules/unused-shared-export.js.map +1 -0
  230. package/src/lib/design/system/rules/upward-dependency.d.ts +4 -0
  231. package/src/lib/design/system/rules/upward-dependency.js +17 -0
  232. package/src/lib/design/system/rules/upward-dependency.js.map +1 -0
  233. package/src/lib/design/system/rules/widths.d.ts +4 -0
  234. package/src/lib/design/system/rules/widths.js +14 -0
  235. package/src/lib/design/system/rules/widths.js.map +1 -0
  236. package/src/lib/design/system/sketch/baseline.d.ts +38 -0
  237. package/src/lib/design/system/sketch/baseline.js +66 -0
  238. package/src/lib/design/system/sketch/baseline.js.map +1 -0
  239. package/src/lib/design/system/sketch/check-format.d.ts +17 -0
  240. package/src/lib/design/system/sketch/check-format.js +56 -0
  241. package/src/lib/design/system/sketch/check-format.js.map +1 -0
  242. package/src/lib/design/system/sketch/check-sketches.d.ts +38 -0
  243. package/src/lib/design/system/sketch/check-sketches.js +104 -0
  244. package/src/lib/design/system/sketch/check-sketches.js.map +1 -0
  245. package/src/lib/design/system/sketch/cli-args.d.ts +26 -0
  246. package/src/lib/design/system/sketch/cli-args.js +63 -0
  247. package/src/lib/design/system/sketch/cli-args.js.map +1 -0
  248. package/src/lib/design/system/sketch/duplicate-class.d.ts +14 -0
  249. package/src/lib/design/system/sketch/duplicate-class.js +57 -0
  250. package/src/lib/design/system/sketch/duplicate-class.js.map +1 -0
  251. package/src/lib/design/system/sketch/merge-preview.d.ts +15 -0
  252. package/src/lib/design/system/sketch/merge-preview.js +43 -0
  253. package/src/lib/design/system/sketch/merge-preview.js.map +1 -0
  254. package/src/lib/design/system/sketch/merge-shared.d.ts +25 -0
  255. package/src/lib/design/system/sketch/merge-shared.js +53 -0
  256. package/src/lib/design/system/sketch/merge-shared.js.map +1 -0
  257. package/src/lib/design/system/sketch/merge.d.ts +31 -0
  258. package/src/lib/design/system/sketch/merge.js +148 -0
  259. package/src/lib/design/system/sketch/merge.js.map +1 -0
  260. package/src/lib/design/system/sketch/merged-parts.d.ts +59 -0
  261. package/src/lib/design/system/sketch/merged-parts.js +134 -0
  262. package/src/lib/design/system/sketch/merged-parts.js.map +1 -0
  263. package/src/lib/design/system/sketch/no-visible-change.d.ts +15 -0
  264. package/src/lib/design/system/sketch/no-visible-change.js +70 -0
  265. package/src/lib/design/system/sketch/no-visible-change.js.map +1 -0
  266. package/src/lib/design/system/sketch/overlay.d.ts +20 -0
  267. package/src/lib/design/system/sketch/overlay.js +38 -0
  268. package/src/lib/design/system/sketch/overlay.js.map +1 -0
  269. package/src/lib/design/system/sketch/part-definition.d.ts +31 -0
  270. package/src/lib/design/system/sketch/part-definition.js +157 -0
  271. package/src/lib/design/system/sketch/part-definition.js.map +1 -0
  272. package/src/lib/design/system/sketch/shared-class.d.ts +9 -0
  273. package/src/lib/design/system/sketch/shared-class.js +65 -0
  274. package/src/lib/design/system/sketch/shared-class.js.map +1 -0
  275. package/src/lib/design/system/sketch/sketch.d.ts +267 -0
  276. package/src/lib/design/system/sketch/sketch.js +568 -0
  277. package/src/lib/design/system/sketch/sketch.js.map +1 -0
  278. package/src/lib/design/system/sketch/unchanged-states.d.ts +23 -0
  279. package/src/lib/design/system/sketch/unchanged-states.js +144 -0
  280. package/src/lib/design/system/sketch/unchanged-states.js.map +1 -0
  281. package/src/lib/design/system/sketch/unused-files.d.ts +10 -0
  282. package/src/lib/design/system/sketch/unused-files.js +56 -0
  283. package/src/lib/design/system/sketch/unused-files.js.map +1 -0
  284. package/src/lib/design/system/system-command.d.ts +9 -0
  285. package/src/lib/design/system/system-command.js +56 -0
  286. package/src/lib/design/system/system-command.js.map +1 -0
  287. package/src/lib/design/system/system-modules.d.ts +58 -0
  288. package/src/lib/design/system/system-modules.js +189 -0
  289. package/src/lib/design/system/system-modules.js.map +1 -0
  290. package/src/lib/design/system/system-tsconfig.d.ts +26 -0
  291. package/src/lib/design/system/system-tsconfig.js +68 -0
  292. package/src/lib/design/system/system-tsconfig.js.map +1 -0
  293. package/src/lib/design/system/ts-source.d.ts +13 -0
  294. package/src/lib/design/system/ts-source.js +108 -0
  295. package/src/lib/design/system/ts-source.js.map +1 -0
  296. package/src/lib/design/system-use.js.map +1 -1
  297. package/src/lib/design/thumb.d.ts +48 -4
  298. package/src/lib/design/thumb.js +127 -37
  299. package/src/lib/design/thumb.js.map +1 -1
  300. package/src/lib/design/typecheck.d.ts +29 -0
  301. package/src/lib/design/typecheck.js +77 -0
  302. package/src/lib/design/typecheck.js.map +1 -0
  303. package/src/lib/design/variant-diff.d.ts +64 -0
  304. package/src/lib/design/variant-diff.js +92 -0
  305. package/src/lib/design/variant-diff.js.map +1 -0
  306. package/src/lib/design/vite-paths.d.ts +2 -0
  307. package/src/lib/design/vite-paths.js +10 -1
  308. package/src/lib/design/vite-paths.js.map +1 -1
  309. package/src/lib/doctor/doctor-command.js.map +1 -1
  310. package/src/lib/doctor/index.js.map +1 -1
  311. package/src/lib/index.js.map +1 -1
  312. package/src/lib/invocation.js +6 -2
  313. package/src/lib/invocation.js.map +1 -1
  314. package/src/lib/playwright/failure-reporter.d.ts +35 -0
  315. package/src/lib/playwright/failure-reporter.js +66 -0
  316. package/src/lib/playwright/failure-reporter.js.map +1 -0
  317. package/src/lib/release.js.map +1 -1
  318. package/src/lib/update/agent-rules-sync.js.map +1 -1
  319. package/src/lib/update/agent-setup.js.map +1 -1
  320. package/src/lib/update/ai.js.map +1 -1
  321. package/src/lib/update/args.js.map +1 -1
  322. package/src/lib/update/available-migrations.js.map +1 -1
  323. package/src/lib/update/commits.js.map +1 -1
  324. package/src/lib/update/gitignore.js.map +1 -1
  325. package/src/lib/update/index.js.map +1 -1
  326. package/src/lib/update/migration-manifest.js.map +1 -1
  327. package/src/lib/update/migration-record.js.map +1 -1
  328. package/src/lib/update/migrations-command.js.map +1 -1
  329. package/src/lib/update/package-manager.d.ts +1 -1
  330. package/src/lib/update/package-manager.js.map +1 -1
  331. package/src/lib/update/packages.js.map +1 -1
  332. package/src/lib/update/pending.js.map +1 -1
  333. package/src/lib/update/plan.js.map +1 -1
  334. package/src/lib/update/registry.js.map +1 -1
  335. package/src/lib/update/run-migrations.js.map +1 -1
  336. package/src/lib/update/scan.js.map +1 -1
  337. package/src/lib/update/semver.js.map +1 -1
  338. package/src/lib/update/tasks.js.map +1 -1
  339. package/src/lib/update/update-command.js.map +1 -1
  340. package/src/lib/utils.js.map +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,203 @@
1
1
  # @ethlete/cli
2
2
 
3
+ ## 3.0.0-next.23
4
+
5
+ ### Minor Changes
6
+
7
+ - `et design shot --variants --crop changed` cuts every cell to one box around the pixels the shown variants change from the merged part, and prints that box for a later `--crop`.
8
+ - Add `@ethlete/cli/playwright/failure-reporter`, a Playwright reporter that writes one JSON line per failed test attempt while the run goes on.
9
+ - The Playwright failure reporter writes a `detail` field: the first line past the kept error lines that holds a URL or a diff line, so a refused request stays visible.
10
+ - Reject and reopen single sketch variants with `et design sketch reject|reopen` or in Studio; a rejected variant keeps its reason, origin and history and can no longer be picked or merged.
11
+ - Studio's sketch view gets a "Changed area" switch per round that shows every column cropped to the one area the round's variants change, enlarged, under a small whole-page thumbnail, and the design server answers that area on `/__design/sketch-crop`.
12
+
13
+ ### Patch Changes
14
+
15
+ - `et design` commands that render Angular markup now exit after they print their result.
16
+ - A sketch state whose markup differs or only renders in a browser now counts as unchanged when its pictures match the product. Studio folds such states and shows a count with a "Show all states" button.
17
+ - `design shot --state <s> --variants` shows a neutral "New state - no merged version" cell when only the variants have the state, instead of a red render error.
18
+ - `et design shot --variants` takes `--round <key>` and `--variant <name>` to draw only the current round next to the merged row.
19
+ - A design check now reports a type checker that failed without output as `NO-TYPE-CHECKER`, with the command and the process error, instead of an empty `TYPE-ERROR`.
20
+
21
+ ## 3.0.0-next.22
22
+
23
+ ### Major Changes
24
+
25
+ - Every agent write in `et design` (`sketch new|report|ready|rebase|merge|discard`, `inbox done`, `comment reply`) needs `--by <model id>`. `sketch report` takes `--variant`, records authors per variant in `sketch.json`, and a merge writes them into the part's `madeBy`.
26
+
27
+ ### Minor Changes
28
+
29
+ - Design comments have an addressed state: the agent and the cli mark a thread addressed, and only the user resolves it in Studio.
30
+ - `design check` and Studio warn of a merged part whose files changed outside a sketch.
31
+ - A design comment takes an `interaction` (rest by default, also `--interaction` on `et design comment`); a comment file without one is on rest.
32
+ - `et design comment reply --question` marks a reply that waits for the user. Comments carry a `threadState` (`agent`, `question` or `answered`) in the Studio API and `inbox --json`.
33
+ - Comment threads: `et design comment reply <id> <text>` answers a comment, and `et design inbox` lists threads where the agent spoke last under "waiting for the user".
34
+ - A design comment stores `size` (a preset name, a width or `fitted`) in place of `preset`, `et design comment` takes `--size`, and the design server signals comment changes on `/__design/comment-events`.
35
+ - Rule `FLOW` accepts a frame that names a page existing only as an open sketch, checked against the states of the sketch's first variant. A name that exists nowhere still fails.
36
+ - Read a product's flows from `flows/<name>.json`, list them at `/__design/flows`, and fail `et design check` on a flow frame that names a missing state.
37
+ - The design server reloads only the frames of a part whose file was added or removed, and `et design inbox done --by` now records the id in the variant's authors or the part's `madeBy`.
38
+ - BREAKING: `et design inbox done <id>` now needs `--note "<reason>"`, which replaces the part's note; Studio withdraws a comment through the new `comment-withdraw`.
39
+ - Rule `WIREFRAME-KIT` lets an element import the wireframe kit, so a rough shell can be an element the pages import. Components and foundations still may not.
40
+ - The design server marks each part's root element with `data-et-part`, and `/__design/sketch-compare` draws every state that shows a sketched part before and after a merge, splits them by pixel change and returns the part's outlines.
41
+ - `et design inbox` lists a sketch whose baseline is stale as "needs rebase", with the changed parts. A merge the design server refuses for a stale baseline answers with `changed` and the `rebase` command.
42
+ - A pointed design note stores the `size` it was written at. `et design check` fails a pointed note without a size, or with a size the product no longer has.
43
+ - A design part can list the `interactions` Studio draws it in, and a frame can force `:hover`, `:focus-visible`, `:active` or disabled on the part's root element through `interaction=<name>`.
44
+ - `definePart` keeps a part's state names in its type, and `render` takes `{ interaction }` to force hover, focus-visible, active or disabled on the drawing's root.
45
+ - A design part imports another part's stylesheet as `@system/<layer>/<name>.css?inline`, resolved per product and sketch, so base parts draw with a product's own tokens. A part that comes or goes redraws its whole product.
46
+ - `GET /__design/system` lists for each part the pages that use it through the import graph, also in products that use its product as base.
47
+ - A design part can declare container `widths`, and the shot route draws a part at a `width` or fitted to its content when the address names no preset.
48
+ - The design server sends a `parts` event with the parts a file change redraws, per product and sketch variant, or `all`, and drops exactly those cached shots.
49
+ - `et design product new <name>` creates a design product: its `config.json` entry and the empty layer folders. `--base` sets the base, and `--from-project` copies the render settings of a project.
50
+ - `et design sketch variant <target> <name> --from <variant>` copies a variant into a new one and reopens the sketch; `sketch.json` groups variants into `rounds`, and `--round <title> --note <text>` starts a new round.
51
+ - Sketch variants share code through `@shared/<path>`, `sketch new --like` copies a part or variant, and `embedPage()` draws a page inside a page.
52
+ - The design server's sketch list carries the states and widths of the target as each variant draws it, and the agent's live report with the variant it names.
53
+ - Pages can draw wireframes with the grey kit from `@design-system/kit` (box, text lines, image, button, input, icon); `et design check` fails a part outside pages that imports it, and the system list counts each page's kit calls.
54
+ - `et design sketch check --fix` writes the variants in the checkout's Prettier format before it checks them; a FORMAT problem points to it.
55
+ - `et design sketch check` warns of a variant that restyles a class of the sketch's `shared/` folder and of a variant file nothing imports.
56
+ - `et design sketch new` takes `--variant-note`, `sketch variant` takes several variants, and sketch commands format their output, explain each flag and infer `--product`.
57
+ - `et design sketch round` edits an existing round, and `sketch list` shows the rounds of each sketch.
58
+ - The sketch listing for Studio names, per variant, the states it renders to the same markup and styles as the product does now (`unchanged`), cached by the part folder hashes.
59
+ - `design check` warns of an export in a part's `shared/` that nothing imports, and `sketch check` of a sketch `shared/` export a variant does not import.
60
+ - `sketch new --variant-note` keeps each variant's note in `sketch.json` instead of overwriting the part's merged note; Studio shows it in the variant's head, and a merge gives it to a new part.
61
+ - A sketch variant may hold its own copy of a shared file at `<variant>/shared/<path>`: `@shared/<path>` resolves to it first in rendering, typechecks and checks, and a merge of that variant carries it.
62
+ - The wireframe kit draws for narrow presets: a row `box` stacks below `stackBelow`, `image` takes a `ratio`, buttons and placeholders end with "…", boxes set the kit text colour, and `heading(text)` draws titles.
63
+ - Add `et design accept <layer>/<name> --by <model id> --note <why>`, which records a direct edit of a merged part the user approved so `design check` stops warning of it.
64
+ - `design shot --asset <file>` draws an image file into a PNG, and `design token <name> --hex` prints the colour a product token resolves to.
65
+ - The design render check warns of grid rows that leave out interaction columns other rows fill (`GRID-COVERAGE`).
66
+ - `et design check` also type-checks each variant of an open sketch and fails with a `TYPE-ERROR` line per broken variant. A merge's own check still types only the product.
67
+ - `design check` and `design sketch check` draw every state headlessly and report `OVERFLOW`, `CLIPPED`, `WRAP`, `ZERO-SIZE` and `RENDER` problems. A part opts out with `definePart({ checks: { skip: [...] } })`.
68
+ - `design check` warns of a `.replace` on markup that neither the part nor a part it uses draws (`STALE-REPLACE`).
69
+ - The design render check warns of a class with a product prefix that no stylesheet of the frame defines (`UNDEFINED-CLASS`).
70
+ - `et design sketch merge` now merges only the picked variant and refuses on parts changed since the sketch began (`sketch rebase` clears it); `check` flags stale notes and the system list carries product themes.
71
+ - The `CLIPPED` render check also reports an outline (width plus offset) or a crisp outer box shadow (no blur) that passes the nearest clipping ancestor or the frame. A soft shadow with blur is not counted.
72
+ - The `CLIPPED` render check also reports an underline (offset, thickness, and a wavy line's swing) that a clipping ancestor cuts off below the text.
73
+ - `forceInteraction` from `@design-system` forces an interaction on one element of a part's markup, not only its root.
74
+ - Every design frame loads its product's `foundations/tokens` stylesheet before the part's styles, so a part no longer imports the tokens itself.
75
+ - A design frame and its shot take `fill=<token>`: the page paints its background with that token in place of the product's page fill.
76
+ - The render check reads each drawn part's `grid`: `sketch check` prints `grid <rows> x <columns> ok` or the states it leaves out, and a state in two cells or a cell naming no state fails as `GRID`.
77
+ - `interactionRows` from `@design-system` builds a part's states and grid from one props object per row and a list of interactions.
78
+ - `et design serve` lists, writes, changes and deletes notes on states (`<product>/notes/<id>.json`) and free notes in a flow file's `notes`. A stale note now fails the check under rule `NOTE`; the flow rule checks free notes.
79
+ - `et design part remove <layer>/<name>` deletes a merged part with its `merged.json` entry and tsconfig path, and resolves its comments. It refuses a part another part imports, a base part, and a part with an open sketch.
80
+ - Design: hide inherited parts, clarify new-part merging, and protect parts imported by application source.
81
+ - `definePart({ styles })` exposes a part's stylesheet on its default export and adds it to every drawing of the part.
82
+ - POST `/__design/product` accepts `@ethlete/components` as `base`, like `--base @ethlete/components`.
83
+ - The design server creates a product on POST `/__design/product` with `{ name, base? }`, using the code of `et design product new`. `base` names another product of the checkout.
84
+ - A design server not started by Studio listens on a free port, so it never takes the port Studio expects.
85
+ - The design server answers Studio's sketch and comment requests: it lists open sketches, previews a merge, writes a pick or discard, merges or discards a sketch, and lists, adds and finishes canvas comments.
86
+ - The design server lists product systems, renders part states in frames and screenshots them. `et design system --json` prints the parts list.
87
+ - `design shot <part> --states --variants --at <commit>` draws a sketch as that commit held it, so a merged sketch can be compared again.
88
+ - Add `et design shot <layer>/<name> --states [--variants]`, which draws every state of a part and every variant of its open sketch into one labelled contact-sheet PNG and prints its path.
89
+ - `et design sketch` opens, checks, merges and discards sketches of a part. `et design inbox` lists open comments and the user's sketch decisions.
90
+ - `definePart` takes an optional `grid` that lays out small states as rows and columns, and `design shot --states` draws such a part one sheet row per grid row.
91
+ - `svgAsset` sets a `class` and `attrs` on the root `<svg>` of an asset.
92
+ - Add `svgAsset(raw, { size })` to `@design-system`, fail `design check` on an SVG asset without a root `<svg>` or the SVG namespace, print each variant's `assets/` folder, and add a pixel diff row to `design shot --variants`.
93
+ - `et design` supports product design systems with layers, a base and presets. `et design check --product <name>` checks a product's layer rules, colours, component copies and motion.
94
+ - The design render check reports `UNKNOWN-THEME` when a drawing uses a colour theme that no stylesheet of its frame defines.
95
+ - A design part may export named helpers next to its `definePart` default, and other parts import them from `@system/<layer>/<name>`.
96
+ - Closing a design comment thread (inbox done, a new sketch round, a merge or discard) now resolves it instead of deleting it, so it can be looked up later.
97
+ - `sketch round --drops <state,state>` lets a round remove part states on purpose, so `sketch check` and `sketch ready` no longer fail those variants for the dropped states.
98
+ - The design server answers `/__design/sketch-diff` with the box of the pixels a sketch variant changes per state against the merged part, cached per pair of pictures.
99
+
100
+ ### Patch Changes
101
+
102
+ - `sketch ready` no longer fails its auto merge on a problem the product already had, and runs the check and the auto merge again on a sketch that is already ready.
103
+ - `/__design/shot` also draws a call variant: `call`, `variant` and `width` give a full-height PNG at that width, kept until a file of the call changes.
104
+ - `et design sketch check` reports an `@shared/` import that resolves to no file in the sketch's `shared/` folder.
105
+ - Design parts and stylesheets load product assets as `@assets/<file>`.
106
+ - `et design check --tsconfig` also typechecks each variant of an open sketch and prints the tsc output of a failing one.
107
+ - Fix the type errors and lint errors of the design sources, write the editor tsconfig of each product while the design server runs, and update two stale design specs.
108
+ - Design comments written in the same millisecond list in the order they were written.
109
+ - The design server picks up changes to `.ethlete/design/config.json` while it runs, and keeps the last good config if the file becomes invalid.
110
+ - A foundations part may import another foundations part, so a logo or an overview can draw with the product tokens. `et design check` still flags a foundation that imports a component, element or page.
111
+ - The design server no longer reloads every frame page on each change; Studio redraws frames from the `parts` event.
112
+ - `et design sketch variant` on a part with no open sketch names the `sketch new` command that starts one.
113
+ - Cover a product stylesheet that registers its drawing folders with Tailwind `@source`, so page sketches compile the utilities the app uses.
114
+ - `et design` copies its own code and frame assets into a per-process folder at start and serves them from there, so rebuilding or reinstalling the cli no longer changes what a running design server serves or renders.
115
+ - `/__design/shot` answers a timeout or a build error with an error status, and `et design` picks up config changes without a restart.
116
+ - `GET /__design/shot` answers 422 with the reason when the part root has zero size or the screenshot fails, instead of waiting out the 30 s Playwright timeout.
117
+ - `et design sketch new` takes `--note <text>`, the note of the first round.
118
+ - `design sketch new` scaffolds `definePart` without a type argument, so the part keeps its state names.
119
+ - The design server's frame page and shot route draw a part as a sketch variant holds it when the address names `sketch` and `variant`.
120
+ - `checks.skip` applies per sketch variant, and `data-et-skip` lets a drawn element opt out of a render check.
121
+ - A design frame now loads its project or product stylesheets in config order, so the last sheet in `styles` always wins.
122
+ - `et design <command> [<subcommand>] --help` prints the usage of that one command and skips its checks.
123
+ - A design product can name several token parts in its config `tokens`; frames load each one's stylesheet in order.
124
+ - `et design check` reports a `var(--token)` without a fallback that no part or stylesheet of the product or its base defines.
125
+ - A design drawing may leave out `styles`: `drawing({ body })` is enough when `definePart({ styles })` carries the stylesheet, and a part's `render` still answers a string of styles.
126
+ - Design: a frame page reports its viewport size with its height, and a frame fitted to its content never shows scrollbars.
127
+ - A part's dependencies include the parts a plain module of its own folder imports, so `sketch check` lists a page that uses a part through such a module, and the rules see the import.
128
+ - A design frame's height message carries its `href` and `errored: true` once the frame failed to draw or threw at runtime.
129
+ - `design sketch check` and `sketch ready` no longer fail on an open comment whose sketch was merged or discarded; they warn and say to reply with `comment reply --by` or have the user resolve it in Studio.
130
+ - `design check` no longer warns with GRID-COVERAGE about a grid row that fills only `rest`. It warns only about a row that fills some interaction columns but not all.
131
+ - `et design sketch ready` names why it did not merge a sketch automatically: the changed states, a failed shot, or a variant that holds another part.
132
+ - `et design sketch report --done` prints "<variant> marked done", also when the variant had no live report.
133
+ - Design validation now checks tokens in reachable shared stylesheets.
134
+ - `et design shot --crop x,y,width,height` and `/__design/shot?clip=x,y,width,height` cut a picture to a box in CSS pixels, so a hairline can be judged with `--scale`.
135
+ - `et design shot --variants --diff-props` compares each variant with the merged part drawn with the variant's state props, so changed props no longer paint the whole diff red.
136
+ - `et design shot --states --variants` lays out the merged row and each variant row by its own `grid`, with a diff row after each grid row.
137
+ - `et design shot` without `--product` takes the only product with an open sketch on the part, else the only product with the part.
138
+ - `et design shot --interaction <hover|focus-visible|active|disabled>` forces an interaction on every cell, and `--help` names it. The design-exploration skill names it.
139
+ - The diff row of `et design shot --variants` says "no pixel change, but the markup differs" when a variant draws other markup to the same pixels.
140
+ - A `/__design/shot` of a state the merged part or the variant lacks answers a 404 that names its states, instead of a 500 drawn as PNG.
141
+ - A `/__design/shot` request without `state=` answers a 400 that names the part's states.
142
+ - `et design shot --variants` on a part that is not merged yet heads the sheet with "new part, nothing to compare with".
143
+ - The diff row of `et design shot --variants` names a state the merged part does not have as "new state", on the sheet and among the printed lines.
144
+ - `et design shot` draws a non-page part with `widths` at its first width unless `--width` or `--preset` is given.
145
+ - `et design shot` prints the PNG path after the diff lines.
146
+ - `et design shot --scale <1-4>` and `/__design/shot?scale=` draw a part at a device scale, so a small part is legible. The design-exploration skill names it.
147
+ - `et design shot --state <a>,<b>` draws only the named states, side by side, and names the states of the part when one does not exist. The design-exploration skill names it.
148
+ - `sketch check` and `sketch ready` fail only on problems a variant brings in. A problem the product already had prints as `NOTE (was there before)`, as the merge does.
149
+ - `sketch new` prints the next steps: `sketch report` while you draw a variant, `report --done` only after a report, the round note when it is missing, then `sketch check` and `sketch ready`.
150
+ - `et design sketch new --round` requires `--note`, so `sketch ready` no longer stops on a round without a note.
151
+ - `et design sketch variant` refuses a sketch the user discarded and asks for a variant pick first; a pick is still cleared.
152
+ - The UNDEFINED-TOKEN check of `et design check` and `et design sketch check` also reads the stylesheets a part imports, and the tokens any file of the part folder sets.
153
+ - The wireframe kit draws more control types: `select`, `tabs`, `segmented`, `checkbox`, `badge` and `dot`.
154
+ - A wireframe kit `box` takes `fill: true` to be at least as tall as the frame, so a page or a shell needs no hand-written `min-height`.
155
+ - A wireframe kit `icon` is a rounded square instead of a circle that read as a radio button, and takes a `glyph` name (`'plus'`, `'close'`, `'menu'`, …) drawn inside it.
156
+ - Every `et design` command finds `.ethlete/design/config.json` in a parent folder, so a sketch report works from a subfolder.
157
+ - Studio refreshes its frames when optimized dependencies change, preventing stale module links after part removal.
158
+ - Write a gitignored `tsconfig.json` into `.ethlete/design` and each product folder so editors resolve `@design-system` and `@system/*`, typecheck products and sketch variants through one function, and lint the design frame sources.
159
+ - The pixel diff of `design shot --variants` runs about 3.5 times faster.
160
+ - The design server sends a `flows` event when a flow file changes, and Studio reads the flows again, so a flow edit shows without a server restart.
161
+ - The design frame page posts `Esc` to its parent. The Studio canvas now leaves an active cross-origin frame on `Esc` and takes its height from the frame page.
162
+ - `et design shot` and the render check take a free port even when 5173 is busy. Vite read port 0 as unset.
163
+ - `et design sketch new pages/<name>` scaffolds a page like its siblings: the tokens import, `madeBy` from `--by`, the product widths and no stylesheet.
164
+ - `et design product new --base` no longer writes an empty `fixtures.ts`, so the new product uses its base's fixture set.
165
+ - `et design product new` writes `config.json` in the layout Prettier gives it, so a later format run leaves it unchanged.
166
+ - The `CLIPPED` and `WRAP` render-check lines name their opt-out: `data-et-skip="<RULE>"` on the element, or `definePart({ checks: { skip: ['<RULE>'] } })`.
167
+ - The design server's JSON answers allow any origin, so Studio can read the parts list.
168
+ - The design server takes `--host <address>` (or `DE_HOST`) to listen on another address than localhost, e.g. `0.0.0.0` to reach it over the LAN.
169
+ - `design shot --states --variants` prints the pixel result of each diff cell, and scales each diff cell to the width of its state cell.
170
+ - The design server draws merge compares and plain `/__design/shot` asks before Studio's canvas shots, which now ask with `priority=low`.
171
+ - `design sketch new` and `design sketch variant` create the `assets/` folder of each variant, so the path they print exists.
172
+ - Design servers keep their own Vite cache per checkout, `design shot --variants` draws the merged state first, `sketch check` warns of a class two stylesheets of a part define, and `sketch ready` needs a note on the running round.
173
+ - `et design inbox done --note` on a sketch thread now writes the note into the variant's copy of the part, never into the product.
174
+ - A design merge that changes an asset re-records the other merged parts that use it, so they no longer show as changed outside a sketch, and lists them in the merge output.
175
+ - `et design sketch merge` takes `--note` as optional and keeps the variant's own note without one; `sketch new`, `variant` and `ready` record `--by` as the variant author, and a merge no longer drops `madeBy`.
176
+ - The `CLIPPED` render check measures a scaled `embedPage` at its scaled size, so a page that embeds a scaled page no longer needs `checks: { skip: ['CLIPPED'] }`.
177
+ - A failed design shot answers 500 with the error picture, a shot accepts a preset with its own width, and sketch check lists every drawn state with its result.
178
+ - `et design sketch check` and `sketch ready` now run the checkout's own Prettier over a variant and report each unformatted file, so an unformatted variant can no longer become ready or merge.
179
+ - A sketch merge or discard closes the open comments on its variants instead of refusing, and `sketch variant --round` closes those on its `--from` variant.
180
+ - A comment on a sketch variant now stores the `createdAt` of its sketch, so a later sketch on the same target no longer takes over, addresses or lists the comments of an earlier one.
181
+ - Design sketches compare fresh screenshots instead of reusing pixel results from earlier rendering sessions.
182
+ - `et design sketch check` and `sketch ready` fail when a variant drops a state the product's part has.
183
+ - Sketch checks now flag targets whose metadata cannot be written during merge.
184
+ - `design sketch new` on a merged part with a `shared/` folder moves that folder into the sketch's `shared/` and imports it as `@shared/`, so the variants no longer each hold a copy.
185
+ - `design sketch new` and `sketch variant` print the repo-relative path of each variant's part file.
186
+ - `et design sketch ready` merges a sketch by itself when no state of any variant differs by a pixel from the product, and logs it as `auto` in `accepted.json`.
187
+ - `design sketch ready` compares pixels when a state's markup differs, so a sketch that only changes whitespace in the markup counts as unchanged.
188
+ - `design sketch report` prints a confirmation, and refuses a sketch that is not in progress with a non-zero exit instead of recording a report Studio never shows.
189
+ - `sketch report --variant <key> --done` ends the live "drawing" report of that variant, so Studio shows its frames before `sketch ready`. The design exploration skill names the command.
190
+ - `sketch variant --round` stores why the running round lost in its own `lost` field instead of overwriting the round's note, and `--round-note` sets the new round's note so `sketch ready` accepts it.
191
+ - Studio now folds a sketch state whose styles changed but whose frames draw the same pixels, so CSS a variant adds for one state no longer unfolds every other state.
192
+ - `design sketch variant` without `--round` now fails while the sketch has open comments. It lists them and names the `--round` command, so an answer to comments starts a new round.
193
+ - A design server no longer outlives a Studio that dies without a clean exit.
194
+ - Remove the host commands only the old Calls view used: call statuses, verdicts, modes, closed states, new variants and check receipts.
195
+ - Restore the host command that reports sketch report changes, so a drawing agent's progress reaches Studio live again.
196
+ - Prevent macOS WebKit freezes when design frames reload, and keep previous content visible while asynchronous drawings finish loading.
197
+ - Design check warns about unscoped selectors in globally loaded token stylesheets.
198
+ - Studio no longer folds a sketch state as unchanged after the sketch's `shared/` folder or the variant's assets or fixtures change: the unchanged-states cache now keys on them too.
199
+ - Update all dependencies to their latest versions (Nx 24, Vitest 5, jsdom 30, ts-morph 28, @types/node 26 and others). The optional `@nx/devkit` peer dependency now requires `^24.0.0`, and the optional `ts-morph` peer of `@ethlete/core` requires `^28.0.0`.
200
+
3
201
  ## 3.0.0-next.21
4
202
 
5
203
  ### Minor Changes
@@ -0,0 +1,9 @@
1
+ declare module '@assets/*?raw' {
2
+ const text: string;
3
+ export default text;
4
+ }
5
+
6
+ declare module '@assets/*' {
7
+ const url: string;
8
+ export default url;
9
+ }
@@ -9,8 +9,13 @@ const flatten = (value: unknown): string => {
9
9
  return String(value);
10
10
  };
11
11
 
12
- const join = (strings: TemplateStringsArray, values: unknown[]) =>
13
- strings.reduce((out, part, index) => out + flatten(values[index - 1]) + part);
12
+ const join = (strings: TemplateStringsArray, values: unknown[]) => {
13
+ let out = strings[0] ?? '';
14
+
15
+ for (let index = 1; index < strings.length; index++) out += flatten(values[index - 1]) + strings[index];
16
+
17
+ return out;
18
+ };
14
19
 
15
20
  /**
16
21
  * Marks a template literal as HTML, so Prettier formats it and an editor highlights it. It escapes
@@ -43,11 +48,13 @@ export type NgField = { value: unknown; errors?: (string | { kind: string; messa
43
48
  export type NgOptions = { imports?: string[]; fields?: Record<string, NgField> };
44
49
 
45
50
  const render = async (template: string, options: NgOptions) => {
51
+ const params = new URLSearchParams(location.search);
46
52
  const response = await fetch('/__design/render', {
47
53
  method: 'POST',
48
54
  headers: { 'content-type': 'application/json' },
49
55
  body: JSON.stringify({
50
- call: new URLSearchParams(location.search).get('call') ?? '',
56
+ call: params.get('call') ?? '',
57
+ product: params.get('product') ?? undefined,
51
58
  template,
52
59
  imports: options.imports ?? [],
53
60
  fields: options.fields,
@@ -70,14 +77,14 @@ export const ng = async (template: string, options: NgOptions = {}) => {
70
77
  };
71
78
 
72
79
  /** A click on an element that matches `click` inside the drawing shows the state `go`. */
73
- export type StateRule<S extends string> = { click: string; go: S };
80
+ export type StateRule<TState extends string> = { click: string; go: TState };
74
81
 
75
82
  /**
76
83
  * Swaps the content of the drawing's state host for `states[rule.go]` when a click matches a rule.
77
84
  * The first matching rule wins. The swap runs in a View Transition where the browser has one.
78
85
  */
79
86
  export const switchStates =
80
- <S extends string>(states: Record<S, string>, rules: StateRule<S>[]) =>
87
+ <TState extends string>(states: Record<TState, string>, rules: StateRule<TState>[]) =>
81
88
  (root: HTMLElement) => {
82
89
  const host = root.querySelector<HTMLElement>('[data-de-states]');
83
90
 
@@ -107,11 +114,11 @@ export const switchStates =
107
114
  * Renders each named template with `ng()` and resolves with a `body` that shows the first state,
108
115
  * and `on(rules)`, a `setup` that swaps states on a click. Every state's styles load up front.
109
116
  */
110
- export const ngStates = async <S extends string>(templates: Record<S, string>, options: NgOptions = {}) => {
111
- const names = Object.keys(templates) as S[];
117
+ export const ngStates = async <TState extends string>(templates: Record<TState, string>, options: NgOptions = {}) => {
118
+ const names = Object.keys(templates) as TState[];
112
119
  const rendered = await Promise.all(names.map((name) => render(templates[name], options)));
113
120
  const states = Object.fromEntries(names.map((name, index) => [name, rendered[index]?.markup ?? ''])) as Record<
114
- S,
121
+ TState,
115
122
  string
116
123
  >;
117
124
  const first = names[0];
@@ -124,18 +131,18 @@ export const ngStates = async <S extends string>(templates: Record<S, string>, o
124
131
  rendered.flatMap((entry) => entry.styles),
125
132
  emitted,
126
133
  ) + `<div data-de-states="${first}" style="display: contents">${states[first]}</div>`,
127
- on: (rules: StateRule<S>[]) => switchStates(states, rules),
134
+ on: (rules: StateRule<TState>[]) => switchStates(states, rules),
128
135
  };
129
136
  };
130
137
 
131
138
  /**
132
139
  * One drawn answer, as the frame renders it: the markup that goes in the frame root, and the CSS
133
- * that styles it. `setup` is a function or a list of them, run in order once after the markup is in the frame root, to wire behavior such
140
+ * that styles it, left out when `definePart({ styles })` carries it. `setup` is a function or a list of them, run in order once after the markup is in the frame root, to wire behavior such
134
141
  * as a click that restarts an animation. A `<script>` in `body` never runs.
135
142
  */
136
143
  export type Drawing = {
137
144
  body: string;
138
- styles: string;
145
+ styles?: string;
139
146
  setup?: ((root: HTMLElement) => void) | ((root: HTMLElement) => void)[];
140
147
  };
141
148
 
@@ -300,6 +307,8 @@ export type Call = {
300
307
  stage?: CallStage;
301
308
  /** Set by `et design legacy` on a call made before the stage was required. Such a call needs no stage. */
302
309
  legacy?: true;
310
+ /** Set by Ethlete Studio once the call was carried over into a product's design system. */
311
+ transferred?: true;
303
312
  /** Set once the call is done for good: `shipped` when the app implements it, `superseded` when another call replaced it. */
304
313
  closed?: 'shipped' | 'superseded';
305
314
  /** The slug of the call this one is parked on. The call is on hold until that call settles. */
@@ -0,0 +1,102 @@
1
+ import { Drawing } from './define-call';
2
+ import { forceBody } from './mark-part';
3
+
4
+ export { css, drawing, html, ng } from './define-call';
5
+ export type { Drawing } from './define-call';
6
+ export { embedPage } from './embed-page';
7
+ export type { EmbedPageOptions } from './embed-page';
8
+ export { interactionRows } from './interaction-rows';
9
+ export type { InteractionRowsOptions } from './interaction-rows';
10
+ export { svgAsset } from './svg-asset';
11
+ export type { SvgAssetOptions } from './svg-asset';
12
+
13
+ /** An interaction Studio can force on a part's root element. Rest is always drawn and never listed. */
14
+ export type Interaction = 'hover' | 'focus-visible' | 'active' | 'disabled';
15
+
16
+ /**
17
+ * One part of a product's design system. `note` records why the part has its current shape, and
18
+ * `madeBy` the model ids that drew it, written by a sketch merge.
19
+ * `states` names each state it is drawn in with the props for that state, and `render` draws it.
20
+ * `styles` is the part's stylesheet: every drawing of the part gets it before the styles `render` returns, and a
21
+ * host that embeds a piece of the part's markup takes it from the default export.
22
+ * A part that responds to its width lists the container `widths` it is drawn at; without them a
23
+ * component or element is drawn at the size of its content, and a page at every viewport preset.
24
+ * `interactions` lists the interactions each state is also drawn in; without it only rest is drawn.
25
+ * `grid` draws the states it names as one table in rest, see `StateGrid`.
26
+ * `checks.skip` names the render checks of `design check` the part breaks on purpose, such as
27
+ * `CLIPPED` for a part that cuts its content. To skip a check for one element only, put
28
+ * `data-et-skip="CLIPPED"` on it; the check then ignores that element and everything inside it.
29
+ */
30
+ export type Part<TProps, TState extends string = string> = {
31
+ note: string;
32
+ madeBy?: string[];
33
+ states: Record<TState, TProps>;
34
+ styles?: string;
35
+ widths?: number[];
36
+ interactions?: Interaction[];
37
+ grid?: StateGrid<TState>;
38
+ checks?: { skip?: RenderCheck[] };
39
+ render: (props: TProps, options?: RenderOptions) => DrawnPart | Promise<DrawnPart>;
40
+ };
41
+
42
+ /** A drawing of a part: its `styles` hold the part's stylesheet and the ones `render` returned, so a host can add them up. */
43
+ export type DrawnPart = Drawing & { styles: string };
44
+
45
+ /**
46
+ * Small states laid out as one table instead of a frame each: `rows` maps a row label to the state drawn under
47
+ * each of the `columns`, and a missing column leaves its cell empty. States it leaves out are drawn as before.
48
+ */
49
+ export type StateGrid<TState extends string = string, TColumn extends string = string> = {
50
+ columns: readonly TColumn[];
51
+ rows: Record<string, Partial<Record<TColumn, TState>>>;
52
+ };
53
+
54
+ /** A rule of the render check: content larger than its frame, cut off, wrapped onto a second line, or drawn at zero size. */
55
+ export type RenderCheck = 'OVERFLOW' | 'CLIPPED' | 'WRAP' | 'ZERO-SIZE';
56
+
57
+ /** How a composing part draws another one: `interaction` forces it on the root, as a frame does. */
58
+ export type RenderOptions = { interaction?: Interaction };
59
+
60
+ type PartSource<TProps, TState extends string, TColumn extends string> = Omit<
61
+ Part<TProps, TState>,
62
+ 'states' | 'grid' | 'render'
63
+ > & {
64
+ states: Record<TState, NoInfer<TProps>>;
65
+ grid?: { columns: readonly TColumn[]; rows: Record<string, Partial<Record<NoInfer<TColumn>, NoInfer<TState>>>> };
66
+ render: (props: TProps) => Drawing | Promise<Drawing>;
67
+ };
68
+
69
+ /**
70
+ * Forces `interaction` on the first element of `markup`, as a frame forces it on a part's root: for a state that
71
+ * shows one element of the part, such as a row of a list, hovered or disabled.
72
+ */
73
+ export const forceInteraction = (markup: string, interaction: Interaction) => forceBody(markup, interaction);
74
+
75
+ const finished = (
76
+ drawn: Drawing,
77
+ { styles, interaction }: { styles?: string; interaction?: Interaction },
78
+ ): DrawnPart => ({
79
+ ...drawn,
80
+ body: interaction ? forceBody(drawn.body, interaction) : drawn.body,
81
+ styles: [styles, drawn.styles].filter(Boolean).join('\n'),
82
+ });
83
+
84
+ /**
85
+ * Types a part where it is written. A part file default-exports the result, and may export named
86
+ * helpers next to it that other parts import from `@system/<layer>/<name>`. Without type
87
+ * arguments, `TProps` comes from the props `render` takes and the state names stay in the type, so
88
+ * `part.states['open']` is `TProps` and an unknown name does not compile.
89
+ */
90
+ export const definePart = <TProps, const TState extends string = string, const TColumn extends string = string>(
91
+ part: PartSource<TProps, TState, TColumn>,
92
+ ): Part<TProps, TState> => ({
93
+ ...part,
94
+ render: (props, options) => {
95
+ const drawn = part.render(props);
96
+ const interaction = options?.interaction;
97
+
98
+ return drawn instanceof Promise
99
+ ? drawn.then((done) => finished(done, { styles: part.styles, interaction }))
100
+ : finished(drawn, { styles: part.styles, interaction });
101
+ },
102
+ });
@@ -0,0 +1,70 @@
1
+ import { Drawing } from './define-call';
2
+
3
+ /**
4
+ * How a page embeds another: the `state` to draw, the viewport `width` and `height` it is drawn at (`'auto'` grows
5
+ * the frame to the page's content), and the `scale` the frame is shown at.
6
+ */
7
+ export type EmbedPageOptions = { state?: string; width?: number; height?: number | 'auto'; scale?: number };
8
+
9
+ const KEPT = ['product', 'sketch', 'variant', 'surface', 'color'];
10
+
11
+ /** The frame address of `page` in `state`, in the product, sketch variant and theme of the frame at `current`. */
12
+ export const embedSearchOf = (options: { page: string; state: string; current: string }) => {
13
+ const current = new URLSearchParams(options.current);
14
+ const search = new URLSearchParams();
15
+
16
+ for (const key of KEPT) {
17
+ const value = current.get(key);
18
+
19
+ if (value !== null) search.set(key, value);
20
+ }
21
+
22
+ search.set('part', options.page);
23
+ search.set('state', options.state);
24
+
25
+ return search.toString();
26
+ };
27
+
28
+ const attribute = (text: string) => text.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
29
+
30
+ /**
31
+ * Draws another page of the product inside a page, in a frame of its own, so a page shows a page in context without
32
+ * importing it. The embedded page draws as the embedding frame does: the same product, sketch variant and theme.
33
+ * Put the answer's `body` into the page's markup; it needs no styles or setup.
34
+ */
35
+ export const embedPage = (page: `pages/${string}`, options: EmbedPageOptions = {}): Drawing => {
36
+ const { state = 'default', width = 1280, height = 800, scale = 1 } = options;
37
+ const search = embedSearchOf({ page, state, current: globalThis.location?.search ?? '' });
38
+ const auto = height === 'auto';
39
+ const frameHeight = auto ? 0 : height;
40
+ const frame = `<iframe data-et-embed="${attribute(page)}" data-et-embed-scale="${scale}"${auto ? ' data-et-embed-height="auto"' : ''} title="${attribute(page)}" src="/frame.html?${attribute(search)}" style="display:block;border:0;width:${width}px;height:${frameHeight}px;transform:scale(${scale});transform-origin:0 0"></iframe>`;
41
+
42
+ return {
43
+ body:
44
+ scale === 1
45
+ ? frame
46
+ : `<div data-et-embed-box style="overflow:hidden;width:${width * scale}px${auto ? '' : `;height:${frameHeight * scale}px`}">${frame}</div>`,
47
+ styles: '',
48
+ };
49
+ };
50
+
51
+ /**
52
+ * Called by an embedded frame each time it measures itself: marks its iframe drawn and, for `height: 'auto'`, grows
53
+ * the iframe (and the box of a scaled one) to `height`. Does nothing in a frame no page embeds.
54
+ */
55
+ export const fitEmbed = (embed: Element | null, height: number) => {
56
+ if (!(embed as HTMLElement | null)?.hasAttribute('data-et-embed')) return;
57
+
58
+ const frame = embed as HTMLElement;
59
+
60
+ if (frame.dataset['etEmbedHeight'] === 'auto') {
61
+ frame.style.height = `${height}px`;
62
+
63
+ const box = frame.parentElement;
64
+
65
+ if (box?.hasAttribute('data-et-embed-box'))
66
+ box.style.height = `${height * Number(frame.dataset['etEmbedScale'] ?? 1)}px`;
67
+ }
68
+
69
+ frame.dataset['etEmbedDrawn'] = '';
70
+ };
@@ -0,0 +1,32 @@
1
+ export type InteractionRowsOptions<TProps, TInteraction extends string> = {
2
+ interactions: readonly TInteraction[];
3
+ force: (props: TProps, interaction: TInteraction) => TProps;
4
+ };
5
+
6
+ type RowState<TRow extends string, TInteraction extends string> = TRow | `${TRow}-${TInteraction}`;
7
+
8
+ /**
9
+ * Builds the `states` and `grid` of a part from one props object per row: each row is drawn at rest as `<row>` and
10
+ * in each of `interactions` as `<row>-<interaction>`, with the props `force` returns. Spread the result into
11
+ * `definePart`; the built state names stay in its type.
12
+ */
13
+ export const interactionRows = <TProps, const TRow extends string, const TInteraction extends string>(
14
+ rows: Record<TRow, TProps>,
15
+ { interactions, force }: InteractionRowsOptions<TProps, TInteraction>,
16
+ ) => {
17
+ const entries = Object.entries(rows) as [TRow, TProps][];
18
+ const states = Object.fromEntries(
19
+ entries.flatMap(([row, props]) => [
20
+ [row, props],
21
+ ...interactions.map((interaction) => [`${row}-${interaction}`, force(props, interaction)]),
22
+ ]),
23
+ ) as Record<RowState<TRow, TInteraction>, TProps>;
24
+ const gridRows = Object.fromEntries(
25
+ entries.map(([row]) => [
26
+ row,
27
+ Object.fromEntries([['rest', row], ...interactions.map((interaction) => [interaction, `${row}-${interaction}`])]),
28
+ ]),
29
+ ) as Record<TRow, Record<'rest' | TInteraction, RowState<TRow, TInteraction>>>;
30
+
31
+ return { states, grid: { columns: ['rest', ...interactions] as const, rows: gridRows } };
32
+ };
@@ -0,0 +1,66 @@
1
+ import { Drawing } from './define-call';
2
+ import { Interaction, Part, RenderOptions } from './define-part';
3
+
4
+ const SKIPPED = /^(?:\s+|<!--[\s\S]*?-->|<(style|script)\b[\s\S]*?<\/\1\s*>|<link\b[^>]*>)/i;
5
+ const TAG = /^<[a-zA-Z][\w:-]*/;
6
+ const ATTRIBUTES = /^(?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*/;
7
+ const MARK = /(\sdata-et-part=")([^"]*)"/;
8
+
9
+ const attributesAtOf = (body: string) => {
10
+ let at = 0;
11
+
12
+ for (let skipped = SKIPPED.exec(body); skipped; skipped = SKIPPED.exec(body.slice(at))) at += skipped[0].length;
13
+
14
+ const tag = TAG.exec(body.slice(at));
15
+
16
+ return tag ? at + tag[0].length : null;
17
+ };
18
+
19
+ /** Adds `key` to `data-et-part` on the first element of `body`, after any leading styles, scripts, links and comments. */
20
+ export const markBody = (body: string, key: string) => {
21
+ const attributesAt = attributesAtOf(body);
22
+
23
+ if (attributesAt === null) return body;
24
+
25
+ const attributes = ATTRIBUTES.exec(body.slice(attributesAt))?.[0] ?? '';
26
+ const marked = MARK.exec(attributes);
27
+
28
+ if (!marked) return `${body.slice(0, attributesAt)} data-et-part="${key}"${body.slice(attributesAt)}`;
29
+
30
+ const valueEnd = attributesAt + marked.index + (marked[1]?.length ?? 0) + (marked[2]?.length ?? 0);
31
+
32
+ return `${body.slice(0, valueEnd)} ${key}${body.slice(valueEnd)}`;
33
+ };
34
+
35
+ /** Forces `interaction` on the first element of `body` the way a frame forces it on the root of a part. */
36
+ export const forceBody = (body: string, interaction: Interaction) => {
37
+ const attributesAt = attributesAtOf(body);
38
+
39
+ if (attributesAt === null) return body;
40
+
41
+ const forced = interaction === 'disabled' ? ' disabled aria-disabled="true"' : ` data-et-force="${interaction}"`;
42
+
43
+ return `${body.slice(0, attributesAt)}${forced}${body.slice(attributesAt)}`;
44
+ };
45
+
46
+ const markDrawing = <TDrawing extends Drawing>(drawn: TDrawing, key: string): TDrawing =>
47
+ typeof drawn?.body === 'string' ? { ...drawn, body: markBody(drawn.body, key) } : drawn;
48
+
49
+ /** The part with a `render` that marks the first element of each drawing as the root of the part `key`. */
50
+ export const markPart = <TProps, TState extends string>(
51
+ part: Part<TProps, TState>,
52
+ key: string,
53
+ ): Part<TProps, TState> => {
54
+ if (typeof part?.render !== 'function') return part;
55
+
56
+ const render = part.render;
57
+
58
+ return {
59
+ ...part,
60
+ render: (props: TProps, options?: RenderOptions) => {
61
+ const drawn = render.call(part, props, options);
62
+
63
+ return drawn instanceof Promise ? drawn.then((done) => markDrawing(done, key)) : markDrawing(drawn, key);
64
+ },
65
+ };
66
+ };
@@ -0,0 +1,36 @@
1
+ const ROOT_SVG = /<svg\b(?:[^>"']|"[^"]*"|'[^']*')*>/u;
2
+
3
+ export type SvgAssetOptions = { size: number | string; class?: string; attrs?: Record<string, string> };
4
+
5
+ const escapeAttribute = (value: string) => value.replace(/&/gu, '&amp;').replace(/"/gu, '&quot;');
6
+
7
+ const attributePattern = (name: string) =>
8
+ new RegExp(`\\s+${name.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')}\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s/>]+)`, 'gu');
9
+
10
+ /**
11
+ * Sets `width` and `height` of the root `<svg>` of a raw SVG asset (an `@assets/<file>.svg?raw` import) to `size`,
12
+ * keeping its `viewBox`, and sets `class` and `attrs` on it, replacing the asset's own. Throws when the string has
13
+ * no `<svg>` element.
14
+ */
15
+ export const svgAsset = (raw: string, options: SvgAssetOptions) => {
16
+ const masked = raw.replace(/<!--[\s\S]*?-->/gu, (comment) => ' '.repeat(comment.length));
17
+ const root = ROOT_SVG.exec(masked);
18
+
19
+ if (!root) throw new Error('svgAsset: the asset has no <svg> element');
20
+
21
+ const set: Record<string, string> = {
22
+ width: String(options.size),
23
+ height: String(options.size),
24
+ ...(options.class === undefined ? {} : { class: options.class }),
25
+ ...options.attrs,
26
+ };
27
+ const rest = Object.keys(set).reduce(
28
+ (tag, name) => tag.replace(attributePattern(name), ''),
29
+ raw.slice(root.index + '<svg'.length, root.index + root[0].length),
30
+ );
31
+ const added = Object.entries(set)
32
+ .map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`)
33
+ .join('');
34
+
35
+ return `${raw.slice(0, root.index)}<svg${added}${rest}${raw.slice(root.index + root[0].length)}`;
36
+ };