mythik 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1111) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +4 -0
  3. package/README.md +97 -2
  4. package/dist/actions/dispatcher.d.ts +46 -0
  5. package/dist/actions/dispatcher.d.ts.map +1 -0
  6. package/dist/actions/dispatcher.js +366 -0
  7. package/dist/actions/dispatcher.js.map +1 -0
  8. package/dist/actions/middleware.d.ts +27 -0
  9. package/dist/actions/middleware.d.ts.map +1 -0
  10. package/dist/actions/middleware.js +37 -0
  11. package/dist/actions/middleware.js.map +1 -0
  12. package/dist/actions/transaction-engine.d.ts +13 -0
  13. package/dist/actions/transaction-engine.d.ts.map +1 -0
  14. package/dist/actions/transaction-engine.js +96 -0
  15. package/dist/actions/transaction-engine.js.map +1 -0
  16. package/dist/app/app-engine.d.ts +106 -0
  17. package/dist/app/app-engine.d.ts.map +1 -0
  18. package/dist/app/app-engine.js +413 -0
  19. package/dist/app/app-engine.js.map +1 -0
  20. package/dist/auth/cross-tab.d.ts +24 -0
  21. package/dist/auth/cross-tab.d.ts.map +1 -0
  22. package/dist/auth/cross-tab.js +117 -0
  23. package/dist/auth/cross-tab.js.map +1 -0
  24. package/dist/auth/engine.d.ts +16 -0
  25. package/dist/auth/engine.d.ts.map +1 -0
  26. package/dist/auth/engine.js +225 -0
  27. package/dist/auth/engine.js.map +1 -0
  28. package/dist/auth/persistence.d.ts +21 -0
  29. package/dist/auth/persistence.d.ts.map +1 -0
  30. package/dist/auth/persistence.js +97 -0
  31. package/dist/auth/persistence.js.map +1 -0
  32. package/dist/auth/providers/custom-jwt.d.ts +41 -0
  33. package/dist/auth/providers/custom-jwt.d.ts.map +1 -0
  34. package/dist/auth/providers/custom-jwt.js +165 -0
  35. package/dist/auth/providers/custom-jwt.js.map +1 -0
  36. package/dist/auth/providers/mock.d.ts +7 -0
  37. package/dist/auth/providers/mock.d.ts.map +1 -0
  38. package/dist/auth/providers/mock.js +52 -0
  39. package/dist/auth/providers/mock.js.map +1 -0
  40. package/dist/auth/providers/supabase.d.ts +90 -0
  41. package/dist/auth/providers/supabase.d.ts.map +1 -0
  42. package/dist/auth/providers/supabase.js +102 -0
  43. package/dist/auth/providers/supabase.js.map +1 -0
  44. package/dist/auth/rate-limiter.d.ts +29 -0
  45. package/dist/auth/rate-limiter.d.ts.map +1 -0
  46. package/dist/auth/rate-limiter.js +67 -0
  47. package/dist/auth/rate-limiter.js.map +1 -0
  48. package/dist/auth/refresh-engine.d.ts +25 -0
  49. package/dist/auth/refresh-engine.d.ts.map +1 -0
  50. package/dist/auth/refresh-engine.js +59 -0
  51. package/dist/auth/refresh-engine.js.map +1 -0
  52. package/dist/auth/types.d.ts +89 -0
  53. package/dist/auth/types.d.ts.map +1 -0
  54. package/dist/auth/types.js +2 -0
  55. package/dist/auth/types.js.map +1 -0
  56. package/dist/contract/endpoint-map.d.ts +20 -0
  57. package/dist/contract/endpoint-map.d.ts.map +1 -0
  58. package/dist/contract/endpoint-map.js +57 -0
  59. package/dist/contract/endpoint-map.js.map +1 -0
  60. package/dist/contract/engine.d.ts +12 -0
  61. package/dist/contract/engine.d.ts.map +1 -0
  62. package/dist/contract/engine.js +80 -0
  63. package/dist/contract/engine.js.map +1 -0
  64. package/dist/contract/extractor.d.ts +7 -0
  65. package/dist/contract/extractor.d.ts.map +1 -0
  66. package/dist/contract/extractor.js +133 -0
  67. package/dist/contract/extractor.js.map +1 -0
  68. package/dist/contract/index.d.ts +4 -0
  69. package/dist/contract/index.d.ts.map +1 -0
  70. package/dist/contract/index.js +2 -0
  71. package/dist/contract/index.js.map +1 -0
  72. package/dist/contract/matcher.d.ts +13 -0
  73. package/dist/contract/matcher.d.ts.map +1 -0
  74. package/dist/contract/matcher.js +57 -0
  75. package/dist/contract/matcher.js.map +1 -0
  76. package/dist/contract/rules/endpoints-exist.d.ts +3 -0
  77. package/dist/contract/rules/endpoints-exist.d.ts.map +1 -0
  78. package/dist/contract/rules/endpoints-exist.js +29 -0
  79. package/dist/contract/rules/endpoints-exist.js.map +1 -0
  80. package/dist/contract/rules/fields-valid.d.ts +3 -0
  81. package/dist/contract/rules/fields-valid.d.ts.map +1 -0
  82. package/dist/contract/rules/fields-valid.js +39 -0
  83. package/dist/contract/rules/fields-valid.js.map +1 -0
  84. package/dist/contract/rules/params-match.d.ts +3 -0
  85. package/dist/contract/rules/params-match.d.ts.map +1 -0
  86. package/dist/contract/rules/params-match.js +40 -0
  87. package/dist/contract/rules/params-match.js.map +1 -0
  88. package/dist/contract/rules/permissions-consistent.d.ts +3 -0
  89. package/dist/contract/rules/permissions-consistent.d.ts.map +1 -0
  90. package/dist/contract/rules/permissions-consistent.js +52 -0
  91. package/dist/contract/rules/permissions-consistent.js.map +1 -0
  92. package/dist/contract/types.d.ts +66 -0
  93. package/dist/contract/types.d.ts.map +1 -0
  94. package/dist/contract/types.js +2 -0
  95. package/dist/contract/types.js.map +1 -0
  96. package/dist/data/data-sources.d.ts +26 -0
  97. package/dist/data/data-sources.d.ts.map +1 -0
  98. package/dist/data/data-sources.js +218 -0
  99. package/dist/data/data-sources.js.map +1 -0
  100. package/dist/data/fetch.d.ts +29 -0
  101. package/dist/data/fetch.d.ts.map +1 -0
  102. package/dist/data/fetch.js +63 -0
  103. package/dist/data/fetch.js.map +1 -0
  104. package/dist/data/offline.d.ts +29 -0
  105. package/dist/data/offline.d.ts.map +1 -0
  106. package/dist/data/offline.js +48 -0
  107. package/dist/data/offline.js.map +1 -0
  108. package/dist/data/realtime.d.ts +21 -0
  109. package/dist/data/realtime.d.ts.map +1 -0
  110. package/dist/data/realtime.js +40 -0
  111. package/dist/data/realtime.js.map +1 -0
  112. package/dist/derive/evaluator.d.ts +19 -0
  113. package/dist/derive/evaluator.d.ts.map +1 -0
  114. package/dist/derive/evaluator.js +59 -0
  115. package/dist/derive/evaluator.js.map +1 -0
  116. package/dist/derive/topo-sort.d.ts +10 -0
  117. package/dist/derive/topo-sort.d.ts.map +1 -0
  118. package/dist/derive/topo-sort.js +36 -0
  119. package/dist/derive/topo-sort.js.map +1 -0
  120. package/dist/design/animation/cascade-types.d.ts +23 -0
  121. package/dist/design/animation/cascade-types.d.ts.map +1 -0
  122. package/dist/design/animation/cascade-types.js +2 -0
  123. package/dist/design/animation/cascade-types.js.map +1 -0
  124. package/dist/design/animation/cascade.d.ts +4 -0
  125. package/dist/design/animation/cascade.d.ts.map +1 -0
  126. package/dist/design/animation/cascade.js +73 -0
  127. package/dist/design/animation/cascade.js.map +1 -0
  128. package/dist/design/animation/css-keyframes.d.ts +8 -0
  129. package/dist/design/animation/css-keyframes.d.ts.map +1 -0
  130. package/dist/design/animation/css-keyframes.js +72 -0
  131. package/dist/design/animation/css-keyframes.js.map +1 -0
  132. package/dist/design/animation/index.d.ts +14 -0
  133. package/dist/design/animation/index.d.ts.map +1 -0
  134. package/dist/design/animation/index.js +9 -0
  135. package/dist/design/animation/index.js.map +1 -0
  136. package/dist/design/animation/keyframes-builder.d.ts +40 -0
  137. package/dist/design/animation/keyframes-builder.d.ts.map +1 -0
  138. package/dist/design/animation/keyframes-builder.js +140 -0
  139. package/dist/design/animation/keyframes-builder.js.map +1 -0
  140. package/dist/design/animation/reanimated-spec.d.ts +16 -0
  141. package/dist/design/animation/reanimated-spec.d.ts.map +1 -0
  142. package/dist/design/animation/reanimated-spec.js +162 -0
  143. package/dist/design/animation/reanimated-spec.js.map +1 -0
  144. package/dist/design/animation/reduced-motion.d.ts +3 -0
  145. package/dist/design/animation/reduced-motion.d.ts.map +1 -0
  146. package/dist/design/animation/reduced-motion.js +54 -0
  147. package/dist/design/animation/reduced-motion.js.map +1 -0
  148. package/dist/design/animation/resolver.d.ts +3 -0
  149. package/dist/design/animation/resolver.d.ts.map +1 -0
  150. package/dist/design/animation/resolver.js +86 -0
  151. package/dist/design/animation/resolver.js.map +1 -0
  152. package/dist/design/animation/types.d.ts +109 -0
  153. package/dist/design/animation/types.d.ts.map +1 -0
  154. package/dist/design/animation/types.js +7 -0
  155. package/dist/design/animation/types.js.map +1 -0
  156. package/dist/design/animation/validator.d.ts +44 -0
  157. package/dist/design/animation/validator.d.ts.map +1 -0
  158. package/dist/design/animation/validator.js +169 -0
  159. package/dist/design/animation/validator.js.map +1 -0
  160. package/dist/design/background/blobs/catalog.d.ts +10 -0
  161. package/dist/design/background/blobs/catalog.d.ts.map +1 -0
  162. package/dist/design/background/blobs/catalog.js +63 -0
  163. package/dist/design/background/blobs/catalog.js.map +1 -0
  164. package/dist/design/background/blobs/index.d.ts +4 -0
  165. package/dist/design/background/blobs/index.d.ts.map +1 -0
  166. package/dist/design/background/blobs/index.js +5 -0
  167. package/dist/design/background/blobs/index.js.map +1 -0
  168. package/dist/design/background/blobs/resolver.d.ts +16 -0
  169. package/dist/design/background/blobs/resolver.d.ts.map +1 -0
  170. package/dist/design/background/blobs/resolver.js +301 -0
  171. package/dist/design/background/blobs/resolver.js.map +1 -0
  172. package/dist/design/background/blobs/types.d.ts +112 -0
  173. package/dist/design/background/blobs/types.d.ts.map +1 -0
  174. package/dist/design/background/blobs/types.js +27 -0
  175. package/dist/design/background/blobs/types.js.map +1 -0
  176. package/dist/design/background/grain.d.ts +3 -0
  177. package/dist/design/background/grain.d.ts.map +1 -0
  178. package/dist/design/background/grain.js +23 -0
  179. package/dist/design/background/grain.js.map +1 -0
  180. package/dist/design/background/index.d.ts +8 -0
  181. package/dist/design/background/index.d.ts.map +1 -0
  182. package/dist/design/background/index.js +8 -0
  183. package/dist/design/background/index.js.map +1 -0
  184. package/dist/design/background/layers.d.ts +3 -0
  185. package/dist/design/background/layers.d.ts.map +1 -0
  186. package/dist/design/background/layers.js +23 -0
  187. package/dist/design/background/layers.js.map +1 -0
  188. package/dist/design/background/patterns/chevron.d.ts +8 -0
  189. package/dist/design/background/patterns/chevron.d.ts.map +1 -0
  190. package/dist/design/background/patterns/chevron.js +10 -0
  191. package/dist/design/background/patterns/chevron.js.map +1 -0
  192. package/dist/design/background/patterns/crosshatch.d.ts +8 -0
  193. package/dist/design/background/patterns/crosshatch.d.ts.map +1 -0
  194. package/dist/design/background/patterns/crosshatch.js +12 -0
  195. package/dist/design/background/patterns/crosshatch.js.map +1 -0
  196. package/dist/design/background/patterns/diagonal.d.ts +8 -0
  197. package/dist/design/background/patterns/diagonal.d.ts.map +1 -0
  198. package/dist/design/background/patterns/diagonal.js +7 -0
  199. package/dist/design/background/patterns/diagonal.js.map +1 -0
  200. package/dist/design/background/patterns/dots.d.ts +7 -0
  201. package/dist/design/background/patterns/dots.d.ts.map +1 -0
  202. package/dist/design/background/patterns/dots.js +9 -0
  203. package/dist/design/background/patterns/dots.js.map +1 -0
  204. package/dist/design/background/patterns/grid.d.ts +7 -0
  205. package/dist/design/background/patterns/grid.d.ts.map +1 -0
  206. package/dist/design/background/patterns/grid.js +8 -0
  207. package/dist/design/background/patterns/grid.js.map +1 -0
  208. package/dist/design/background/patterns/index.d.ts +8 -0
  209. package/dist/design/background/patterns/index.d.ts.map +1 -0
  210. package/dist/design/background/patterns/index.js +8 -0
  211. package/dist/design/background/patterns/index.js.map +1 -0
  212. package/dist/design/background/patterns/iso.d.ts +7 -0
  213. package/dist/design/background/patterns/iso.d.ts.map +1 -0
  214. package/dist/design/background/patterns/iso.js +11 -0
  215. package/dist/design/background/patterns/iso.js.map +1 -0
  216. package/dist/design/background/patterns/resolver.d.ts +3 -0
  217. package/dist/design/background/patterns/resolver.d.ts.map +1 -0
  218. package/dist/design/background/patterns/resolver.js +56 -0
  219. package/dist/design/background/patterns/resolver.js.map +1 -0
  220. package/dist/design/background/primitives/gradient.d.ts +3 -0
  221. package/dist/design/background/primitives/gradient.d.ts.map +1 -0
  222. package/dist/design/background/primitives/gradient.js +68 -0
  223. package/dist/design/background/primitives/gradient.js.map +1 -0
  224. package/dist/design/background/primitives/image.d.ts +3 -0
  225. package/dist/design/background/primitives/image.d.ts.map +1 -0
  226. package/dist/design/background/primitives/image.js +12 -0
  227. package/dist/design/background/primitives/image.js.map +1 -0
  228. package/dist/design/background/primitives/index.d.ts +4 -0
  229. package/dist/design/background/primitives/index.d.ts.map +1 -0
  230. package/dist/design/background/primitives/index.js +4 -0
  231. package/dist/design/background/primitives/index.js.map +1 -0
  232. package/dist/design/background/primitives/solid.d.ts +8 -0
  233. package/dist/design/background/primitives/solid.d.ts.map +1 -0
  234. package/dist/design/background/primitives/solid.js +15 -0
  235. package/dist/design/background/primitives/solid.js.map +1 -0
  236. package/dist/design/background/resolver.d.ts +3 -0
  237. package/dist/design/background/resolver.d.ts.map +1 -0
  238. package/dist/design/background/resolver.js +20 -0
  239. package/dist/design/background/resolver.js.map +1 -0
  240. package/dist/design/background/sanitizer.d.ts +2 -0
  241. package/dist/design/background/sanitizer.d.ts.map +1 -0
  242. package/dist/design/background/sanitizer.js +54 -0
  243. package/dist/design/background/sanitizer.js.map +1 -0
  244. package/dist/design/deep-tokens.d.ts +125 -0
  245. package/dist/design/deep-tokens.d.ts.map +1 -0
  246. package/dist/design/deep-tokens.js +115 -0
  247. package/dist/design/deep-tokens.js.map +1 -0
  248. package/dist/design/dna.d.ts +7 -0
  249. package/dist/design/dna.d.ts.map +1 -0
  250. package/dist/design/dna.js +210 -0
  251. package/dist/design/dna.js.map +1 -0
  252. package/dist/design/identity/color.d.ts +8 -0
  253. package/dist/design/identity/color.d.ts.map +1 -0
  254. package/dist/design/identity/color.js +64 -0
  255. package/dist/design/identity/color.js.map +1 -0
  256. package/dist/design/identity/elevation.d.ts +8 -0
  257. package/dist/design/identity/elevation.d.ts.map +1 -0
  258. package/dist/design/identity/elevation.js +31 -0
  259. package/dist/design/identity/elevation.js.map +1 -0
  260. package/dist/design/identity/index.d.ts +12 -0
  261. package/dist/design/identity/index.d.ts.map +1 -0
  262. package/dist/design/identity/index.js +14 -0
  263. package/dist/design/identity/index.js.map +1 -0
  264. package/dist/design/identity/shape.d.ts +4 -0
  265. package/dist/design/identity/shape.d.ts.map +1 -0
  266. package/dist/design/identity/shape.js +23 -0
  267. package/dist/design/identity/shape.js.map +1 -0
  268. package/dist/design/identity/surface.d.ts +18 -0
  269. package/dist/design/identity/surface.d.ts.map +1 -0
  270. package/dist/design/identity/surface.js +125 -0
  271. package/dist/design/identity/surface.js.map +1 -0
  272. package/dist/design/identity/types.d.ts +280 -0
  273. package/dist/design/identity/types.d.ts.map +1 -0
  274. package/dist/design/identity/types.js +21 -0
  275. package/dist/design/identity/types.js.map +1 -0
  276. package/dist/design/identity/typography.d.ts +7 -0
  277. package/dist/design/identity/typography.d.ts.map +1 -0
  278. package/dist/design/identity/typography.js +42 -0
  279. package/dist/design/identity/typography.js.map +1 -0
  280. package/dist/design/identity-resolver.d.ts +15 -0
  281. package/dist/design/identity-resolver.d.ts.map +1 -0
  282. package/dist/design/identity-resolver.js +52 -0
  283. package/dist/design/identity-resolver.js.map +1 -0
  284. package/dist/design/oklch.d.ts +10 -0
  285. package/dist/design/oklch.d.ts.map +1 -0
  286. package/dist/design/oklch.js +61 -0
  287. package/dist/design/oklch.js.map +1 -0
  288. package/dist/design/palette.d.ts +26 -0
  289. package/dist/design/palette.d.ts.map +1 -0
  290. package/dist/design/palette.js +73 -0
  291. package/dist/design/palette.js.map +1 -0
  292. package/dist/design/presets.d.ts +18 -0
  293. package/dist/design/presets.d.ts.map +1 -0
  294. package/dist/design/presets.js +2 -0
  295. package/dist/design/presets.js.map +1 -0
  296. package/dist/design/recipes/animations.d.ts +11 -0
  297. package/dist/design/recipes/animations.d.ts.map +1 -0
  298. package/dist/design/recipes/animations.js +180 -0
  299. package/dist/design/recipes/animations.js.map +1 -0
  300. package/dist/design/recipes/backgrounds.d.ts +3 -0
  301. package/dist/design/recipes/backgrounds.d.ts.map +1 -0
  302. package/dist/design/recipes/backgrounds.js +152 -0
  303. package/dist/design/recipes/backgrounds.js.map +1 -0
  304. package/dist/design/recipes/index.d.ts +2 -0
  305. package/dist/design/recipes/index.d.ts.map +1 -0
  306. package/dist/design/recipes/index.js +2 -0
  307. package/dist/design/recipes/index.js.map +1 -0
  308. package/dist/design/surface-to-css.d.ts +57 -0
  309. package/dist/design/surface-to-css.d.ts.map +1 -0
  310. package/dist/design/surface-to-css.js +130 -0
  311. package/dist/design/surface-to-css.js.map +1 -0
  312. package/dist/design/surface-to-rn.d.ts +57 -0
  313. package/dist/design/surface-to-rn.d.ts.map +1 -0
  314. package/dist/design/surface-to-rn.js +133 -0
  315. package/dist/design/surface-to-rn.js.map +1 -0
  316. package/dist/design/tokens.d.ts +67 -0
  317. package/dist/design/tokens.d.ts.map +1 -0
  318. package/dist/design/tokens.js +36 -0
  319. package/dist/design/tokens.js.map +1 -0
  320. package/dist/device/context.d.ts +13 -0
  321. package/dist/device/context.d.ts.map +1 -0
  322. package/dist/device/context.js +2 -0
  323. package/dist/device/context.js.map +1 -0
  324. package/dist/editor-session/clone.d.ts +4 -0
  325. package/dist/editor-session/clone.d.ts.map +1 -0
  326. package/dist/editor-session/clone.js +55 -0
  327. package/dist/editor-session/clone.js.map +1 -0
  328. package/dist/editor-session/engine.d.ts +21 -0
  329. package/dist/editor-session/engine.d.ts.map +1 -0
  330. package/dist/editor-session/engine.js +375 -0
  331. package/dist/editor-session/engine.js.map +1 -0
  332. package/dist/editor-session/persistence.d.ts +8 -0
  333. package/dist/editor-session/persistence.d.ts.map +1 -0
  334. package/dist/editor-session/persistence.js +89 -0
  335. package/dist/editor-session/persistence.js.map +1 -0
  336. package/dist/editor-session/types.d.ts +90 -0
  337. package/dist/editor-session/types.d.ts.map +1 -0
  338. package/dist/editor-session/types.js +2 -0
  339. package/dist/editor-session/types.js.map +1 -0
  340. package/dist/editor-session/validators.d.ts +5 -0
  341. package/dist/editor-session/validators.d.ts.map +1 -0
  342. package/dist/editor-session/validators.js +106 -0
  343. package/dist/editor-session/validators.js.map +1 -0
  344. package/dist/elements/composer.d.ts +109 -0
  345. package/dist/elements/composer.d.ts.map +1 -0
  346. package/dist/elements/composer.js +129 -0
  347. package/dist/elements/composer.js.map +1 -0
  348. package/dist/export/csv.d.ts +3 -0
  349. package/dist/export/csv.d.ts.map +1 -0
  350. package/dist/export/csv.js +16 -0
  351. package/dist/export/csv.js.map +1 -0
  352. package/dist/export/download.d.ts +2 -0
  353. package/dist/export/download.d.ts.map +1 -0
  354. package/dist/export/download.js +13 -0
  355. package/dist/export/download.js.map +1 -0
  356. package/dist/export/format.d.ts +3 -0
  357. package/dist/export/format.d.ts.map +1 -0
  358. package/dist/export/format.js +43 -0
  359. package/dist/export/format.js.map +1 -0
  360. package/dist/export/types.d.ts +22 -0
  361. package/dist/export/types.d.ts.map +1 -0
  362. package/dist/export/types.js +2 -0
  363. package/dist/export/types.js.map +1 -0
  364. package/dist/expressions/deep-resolve.d.ts +4 -0
  365. package/dist/expressions/deep-resolve.d.ts.map +1 -0
  366. package/dist/expressions/deep-resolve.js +20 -0
  367. package/dist/expressions/deep-resolve.js.map +1 -0
  368. package/dist/expressions/handlers/array.d.ts +19 -0
  369. package/dist/expressions/handlers/array.d.ts.map +1 -0
  370. package/dist/expressions/handlers/array.js +161 -0
  371. package/dist/expressions/handlers/array.js.map +1 -0
  372. package/dist/expressions/handlers/auth.d.ts +13 -0
  373. package/dist/expressions/handlers/auth.d.ts.map +1 -0
  374. package/dist/expressions/handlers/auth.js +90 -0
  375. package/dist/expressions/handlers/auth.js.map +1 -0
  376. package/dist/expressions/handlers/bind.d.ts +22 -0
  377. package/dist/expressions/handlers/bind.d.ts.map +1 -0
  378. package/dist/expressions/handlers/bind.js +57 -0
  379. package/dist/expressions/handlers/bind.js.map +1 -0
  380. package/dist/expressions/handlers/breakpoint.d.ts +10 -0
  381. package/dist/expressions/handlers/breakpoint.d.ts.map +1 -0
  382. package/dist/expressions/handlers/breakpoint.js +39 -0
  383. package/dist/expressions/handlers/breakpoint.js.map +1 -0
  384. package/dist/expressions/handlers/computed.d.ts +3 -0
  385. package/dist/expressions/handlers/computed.d.ts.map +1 -0
  386. package/dist/expressions/handlers/computed.js +20 -0
  387. package/dist/expressions/handlers/computed.js.map +1 -0
  388. package/dist/expressions/handlers/cond.d.ts +3 -0
  389. package/dist/expressions/handlers/cond.d.ts.map +1 -0
  390. package/dist/expressions/handlers/cond.js +57 -0
  391. package/dist/expressions/handlers/cond.js.map +1 -0
  392. package/dist/expressions/handlers/date.d.ts +14 -0
  393. package/dist/expressions/handlers/date.d.ts.map +1 -0
  394. package/dist/expressions/handlers/date.js +95 -0
  395. package/dist/expressions/handlers/date.js.map +1 -0
  396. package/dist/expressions/handlers/format.d.ts +19 -0
  397. package/dist/expressions/handlers/format.d.ts.map +1 -0
  398. package/dist/expressions/handlers/format.js +93 -0
  399. package/dist/expressions/handlers/format.js.map +1 -0
  400. package/dist/expressions/handlers/group.d.ts +25 -0
  401. package/dist/expressions/handlers/group.d.ts.map +1 -0
  402. package/dist/expressions/handlers/group.js +85 -0
  403. package/dist/expressions/handlers/group.js.map +1 -0
  404. package/dist/expressions/handlers/i18n.d.ts +8 -0
  405. package/dist/expressions/handlers/i18n.d.ts.map +1 -0
  406. package/dist/expressions/handlers/i18n.js +35 -0
  407. package/dist/expressions/handlers/i18n.js.map +1 -0
  408. package/dist/expressions/handlers/index.d.ts +4 -0
  409. package/dist/expressions/handlers/index.d.ts.map +1 -0
  410. package/dist/expressions/handlers/index.js +52 -0
  411. package/dist/expressions/handlers/index.js.map +1 -0
  412. package/dist/expressions/handlers/let.d.ts +4 -0
  413. package/dist/expressions/handlers/let.d.ts.map +1 -0
  414. package/dist/expressions/handlers/let.js +36 -0
  415. package/dist/expressions/handlers/let.js.map +1 -0
  416. package/dist/expressions/handlers/logic.d.ts +27 -0
  417. package/dist/expressions/handlers/logic.d.ts.map +1 -0
  418. package/dist/expressions/handlers/logic.js +52 -0
  419. package/dist/expressions/handlers/logic.js.map +1 -0
  420. package/dist/expressions/handlers/math.d.ts +13 -0
  421. package/dist/expressions/handlers/math.d.ts.map +1 -0
  422. package/dist/expressions/handlers/math.js +58 -0
  423. package/dist/expressions/handlers/math.js.map +1 -0
  424. package/dist/expressions/handlers/platform.d.ts +13 -0
  425. package/dist/expressions/handlers/platform.d.ts.map +1 -0
  426. package/dist/expressions/handlers/platform.js +37 -0
  427. package/dist/expressions/handlers/platform.js.map +1 -0
  428. package/dist/expressions/handlers/prop.d.ts +10 -0
  429. package/dist/expressions/handlers/prop.d.ts.map +1 -0
  430. package/dist/expressions/handlers/prop.js +18 -0
  431. package/dist/expressions/handlers/prop.js.map +1 -0
  432. package/dist/expressions/handlers/selection.d.ts +10 -0
  433. package/dist/expressions/handlers/selection.d.ts.map +1 -0
  434. package/dist/expressions/handlers/selection.js +33 -0
  435. package/dist/expressions/handlers/selection.js.map +1 -0
  436. package/dist/expressions/handlers/state.d.ts +3 -0
  437. package/dist/expressions/handlers/state.d.ts.map +1 -0
  438. package/dist/expressions/handlers/state.js +8 -0
  439. package/dist/expressions/handlers/state.js.map +1 -0
  440. package/dist/expressions/handlers/switch.d.ts +15 -0
  441. package/dist/expressions/handlers/switch.d.ts.map +1 -0
  442. package/dist/expressions/handlers/switch.js +31 -0
  443. package/dist/expressions/handlers/switch.js.map +1 -0
  444. package/dist/expressions/handlers/template.d.ts +3 -0
  445. package/dist/expressions/handlers/template.d.ts.map +1 -0
  446. package/dist/expressions/handlers/template.js +23 -0
  447. package/dist/expressions/handlers/template.js.map +1 -0
  448. package/dist/expressions/handlers/token.d.ts +3 -0
  449. package/dist/expressions/handlers/token.d.ts.map +1 -0
  450. package/dist/expressions/handlers/token.js +40 -0
  451. package/dist/expressions/handlers/token.js.map +1 -0
  452. package/dist/expressions/handlers/unique-id.d.ts +11 -0
  453. package/dist/expressions/handlers/unique-id.d.ts.map +1 -0
  454. package/dist/expressions/handlers/unique-id.js +52 -0
  455. package/dist/expressions/handlers/unique-id.js.map +1 -0
  456. package/dist/expressions/registry.d.ts +8 -0
  457. package/dist/expressions/registry.d.ts.map +1 -0
  458. package/dist/expressions/registry.js +35 -0
  459. package/dist/expressions/registry.js.map +1 -0
  460. package/dist/expressions/resolver.d.ts +15 -0
  461. package/dist/expressions/resolver.d.ts.map +1 -0
  462. package/dist/expressions/resolver.js +56 -0
  463. package/dist/expressions/resolver.js.map +1 -0
  464. package/dist/factory.d.ts +56 -0
  465. package/dist/factory.d.ts.map +1 -0
  466. package/dist/factory.js +265 -0
  467. package/dist/factory.js.map +1 -0
  468. package/dist/fetch/framework-fetch.d.ts +12 -0
  469. package/dist/fetch/framework-fetch.d.ts.map +1 -0
  470. package/dist/fetch/framework-fetch.js +36 -0
  471. package/dist/fetch/framework-fetch.js.map +1 -0
  472. package/dist/fetch/interceptors/auth.d.ts +19 -0
  473. package/dist/fetch/interceptors/auth.d.ts.map +1 -0
  474. package/dist/fetch/interceptors/auth.js +66 -0
  475. package/dist/fetch/interceptors/auth.js.map +1 -0
  476. package/dist/fetch/interceptors/logging.d.ts +8 -0
  477. package/dist/fetch/interceptors/logging.d.ts.map +1 -0
  478. package/dist/fetch/interceptors/logging.js +40 -0
  479. package/dist/fetch/interceptors/logging.js.map +1 -0
  480. package/dist/fetch/interceptors/retry.d.ts +20 -0
  481. package/dist/fetch/interceptors/retry.d.ts.map +1 -0
  482. package/dist/fetch/interceptors/retry.js +34 -0
  483. package/dist/fetch/interceptors/retry.js.map +1 -0
  484. package/dist/fetch/interceptors/timeout.d.ts +7 -0
  485. package/dist/fetch/interceptors/timeout.d.ts.map +1 -0
  486. package/dist/fetch/interceptors/timeout.js +20 -0
  487. package/dist/fetch/interceptors/timeout.js.map +1 -0
  488. package/dist/fetch/types.d.ts +30 -0
  489. package/dist/fetch/types.d.ts.map +1 -0
  490. package/dist/fetch/types.js +2 -0
  491. package/dist/fetch/types.js.map +1 -0
  492. package/dist/forms/engine.d.ts +3 -0
  493. package/dist/forms/engine.d.ts.map +1 -0
  494. package/dist/forms/engine.js +167 -0
  495. package/dist/forms/engine.js.map +1 -0
  496. package/dist/forms/types.d.ts +30 -0
  497. package/dist/forms/types.d.ts.map +1 -0
  498. package/dist/forms/types.js +2 -0
  499. package/dist/forms/types.js.map +1 -0
  500. package/dist/i18n/engine.d.ts +15 -0
  501. package/dist/i18n/engine.d.ts.map +1 -0
  502. package/dist/i18n/engine.js +61 -0
  503. package/dist/i18n/engine.js.map +1 -0
  504. package/dist/index.d.ts +161 -0
  505. package/dist/index.d.ts.map +1 -0
  506. package/dist/index.js +106 -0
  507. package/dist/index.js.map +1 -0
  508. package/dist/interactions/data-operations.d.ts +51 -0
  509. package/dist/interactions/data-operations.d.ts.map +1 -0
  510. package/dist/interactions/data-operations.js +64 -0
  511. package/dist/interactions/data-operations.js.map +1 -0
  512. package/dist/interactions/drag-drop.d.ts +14 -0
  513. package/dist/interactions/drag-drop.d.ts.map +1 -0
  514. package/dist/interactions/drag-drop.js +35 -0
  515. package/dist/interactions/drag-drop.js.map +1 -0
  516. package/dist/interactions/export.d.ts +24 -0
  517. package/dist/interactions/export.d.ts.map +1 -0
  518. package/dist/interactions/export.js +44 -0
  519. package/dist/interactions/export.js.map +1 -0
  520. package/dist/interactions/history.d.ts +16 -0
  521. package/dist/interactions/history.d.ts.map +1 -0
  522. package/dist/interactions/history.js +52 -0
  523. package/dist/interactions/history.js.map +1 -0
  524. package/dist/motion/engine.d.ts +47 -0
  525. package/dist/motion/engine.d.ts.map +1 -0
  526. package/dist/motion/engine.js +81 -0
  527. package/dist/motion/engine.js.map +1 -0
  528. package/dist/navigation/router.d.ts +36 -0
  529. package/dist/navigation/router.d.ts.map +1 -0
  530. package/dist/navigation/router.js +51 -0
  531. package/dist/navigation/router.js.map +1 -0
  532. package/dist/permissions/evaluator.d.ts +15 -0
  533. package/dist/permissions/evaluator.d.ts.map +1 -0
  534. package/dist/permissions/evaluator.js +32 -0
  535. package/dist/permissions/evaluator.js.map +1 -0
  536. package/dist/plugins/loader.d.ts +30 -0
  537. package/dist/plugins/loader.d.ts.map +1 -0
  538. package/dist/plugins/loader.js +87 -0
  539. package/dist/plugins/loader.js.map +1 -0
  540. package/dist/reference/generator.d.ts +25 -0
  541. package/dist/reference/generator.d.ts.map +1 -0
  542. package/dist/reference/generator.js +808 -0
  543. package/dist/reference/generator.js.map +1 -0
  544. package/dist/renderer/deps.d.ts +24 -0
  545. package/dist/renderer/deps.d.ts.map +1 -0
  546. package/dist/renderer/deps.js +150 -0
  547. package/dist/renderer/deps.js.map +1 -0
  548. package/dist/renderer/engine.d.ts +15 -0
  549. package/dist/renderer/engine.d.ts.map +1 -0
  550. package/dist/renderer/engine.js +1097 -0
  551. package/dist/renderer/engine.js.map +1 -0
  552. package/dist/renderer/lazy-paths.d.ts +14 -0
  553. package/dist/renderer/lazy-paths.d.ts.map +1 -0
  554. package/dist/renderer/lazy-paths.js +63 -0
  555. package/dist/renderer/lazy-paths.js.map +1 -0
  556. package/dist/renderer/prop-schemas.d.ts +17 -0
  557. package/dist/renderer/prop-schemas.d.ts.map +1 -0
  558. package/dist/renderer/prop-schemas.js +81 -0
  559. package/dist/renderer/prop-schemas.js.map +1 -0
  560. package/dist/renderer/registry.d.ts +10 -0
  561. package/dist/renderer/registry.d.ts.map +1 -0
  562. package/dist/renderer/registry.js +22 -0
  563. package/dist/renderer/registry.js.map +1 -0
  564. package/dist/renderer/variants.d.ts +54 -0
  565. package/dist/renderer/variants.d.ts.map +1 -0
  566. package/dist/renderer/variants.js +105 -0
  567. package/dist/renderer/variants.js.map +1 -0
  568. package/dist/runtime/mount-spec-runtime.d.ts +59 -0
  569. package/dist/runtime/mount-spec-runtime.d.ts.map +1 -0
  570. package/dist/runtime/mount-spec-runtime.js +73 -0
  571. package/dist/runtime/mount-spec-runtime.js.map +1 -0
  572. package/dist/security/api-spec-validator.d.ts +13 -0
  573. package/dist/security/api-spec-validator.d.ts.map +1 -0
  574. package/dist/security/api-spec-validator.js +347 -0
  575. package/dist/security/api-spec-validator.js.map +1 -0
  576. package/dist/security/app-spec-validator.d.ts +4 -0
  577. package/dist/security/app-spec-validator.d.ts.map +1 -0
  578. package/dist/security/app-spec-validator.js +200 -0
  579. package/dist/security/app-spec-validator.js.map +1 -0
  580. package/dist/security/identifier-guard.d.ts +3 -0
  581. package/dist/security/identifier-guard.d.ts.map +1 -0
  582. package/dist/security/identifier-guard.js +15 -0
  583. package/dist/security/identifier-guard.js.map +1 -0
  584. package/dist/security/identity-token-validator.d.ts +3 -0
  585. package/dist/security/identity-token-validator.d.ts.map +1 -0
  586. package/dist/security/identity-token-validator.js +121 -0
  587. package/dist/security/identity-token-validator.js.map +1 -0
  588. package/dist/security/index.d.ts +14 -0
  589. package/dist/security/index.d.ts.map +1 -0
  590. package/dist/security/index.js +8 -0
  591. package/dist/security/index.js.map +1 -0
  592. package/dist/security/protection-registry.d.ts +24 -0
  593. package/dist/security/protection-registry.d.ts.map +1 -0
  594. package/dist/security/protection-registry.js +35 -0
  595. package/dist/security/protection-registry.js.map +1 -0
  596. package/dist/security/rate-limiter.d.ts +23 -0
  597. package/dist/security/rate-limiter.d.ts.map +1 -0
  598. package/dist/security/rate-limiter.js +44 -0
  599. package/dist/security/rate-limiter.js.map +1 -0
  600. package/dist/security/spec-signing.d.ts +23 -0
  601. package/dist/security/spec-signing.d.ts.map +1 -0
  602. package/dist/security/spec-signing.js +62 -0
  603. package/dist/security/spec-signing.js.map +1 -0
  604. package/dist/security/spec-validator.d.ts +60 -0
  605. package/dist/security/spec-validator.d.ts.map +1 -0
  606. package/dist/security/spec-validator.js +903 -0
  607. package/dist/security/spec-validator.js.map +1 -0
  608. package/dist/security/state-protection.d.ts +23 -0
  609. package/dist/security/state-protection.d.ts.map +1 -0
  610. package/dist/security/state-protection.js +49 -0
  611. package/dist/security/state-protection.js.map +1 -0
  612. package/dist/security/url-whitelist.d.ts +14 -0
  613. package/dist/security/url-whitelist.d.ts.map +1 -0
  614. package/dist/security/url-whitelist.js +37 -0
  615. package/dist/security/url-whitelist.js.map +1 -0
  616. package/dist/server.d.ts +6 -0
  617. package/dist/server.d.ts.map +1 -0
  618. package/dist/server.js +8 -0
  619. package/dist/server.js.map +1 -0
  620. package/dist/spec-engine/app-elements.d.ts +5 -0
  621. package/dist/spec-engine/app-elements.d.ts.map +1 -0
  622. package/dist/spec-engine/app-elements.js +36 -0
  623. package/dist/spec-engine/app-elements.js.map +1 -0
  624. package/dist/spec-engine/app-manifest.d.ts +3 -0
  625. package/dist/spec-engine/app-manifest.d.ts.map +1 -0
  626. package/dist/spec-engine/app-manifest.js +132 -0
  627. package/dist/spec-engine/app-manifest.js.map +1 -0
  628. package/dist/spec-engine/elements.d.ts +8 -0
  629. package/dist/spec-engine/elements.d.ts.map +1 -0
  630. package/dist/spec-engine/elements.js +18 -0
  631. package/dist/spec-engine/elements.js.map +1 -0
  632. package/dist/spec-engine/engine.d.ts +3 -0
  633. package/dist/spec-engine/engine.d.ts.map +1 -0
  634. package/dist/spec-engine/engine.js +76 -0
  635. package/dist/spec-engine/engine.js.map +1 -0
  636. package/dist/spec-engine/handlers/api-handler.d.ts +5 -0
  637. package/dist/spec-engine/handlers/api-handler.d.ts.map +1 -0
  638. package/dist/spec-engine/handlers/api-handler.js +111 -0
  639. package/dist/spec-engine/handlers/api-handler.js.map +1 -0
  640. package/dist/spec-engine/handlers/app-handler.d.ts +4 -0
  641. package/dist/spec-engine/handlers/app-handler.d.ts.map +1 -0
  642. package/dist/spec-engine/handlers/app-handler.js +41 -0
  643. package/dist/spec-engine/handlers/app-handler.js.map +1 -0
  644. package/dist/spec-engine/handlers/index.d.ts +8 -0
  645. package/dist/spec-engine/handlers/index.d.ts.map +1 -0
  646. package/dist/spec-engine/handlers/index.js +17 -0
  647. package/dist/spec-engine/handlers/index.js.map +1 -0
  648. package/dist/spec-engine/handlers/screen-handler.d.ts +4 -0
  649. package/dist/spec-engine/handlers/screen-handler.d.ts.map +1 -0
  650. package/dist/spec-engine/handlers/screen-handler.js +34 -0
  651. package/dist/spec-engine/handlers/screen-handler.js.map +1 -0
  652. package/dist/spec-engine/manifest.d.ts +11 -0
  653. package/dist/spec-engine/manifest.d.ts.map +1 -0
  654. package/dist/spec-engine/manifest.js +76 -0
  655. package/dist/spec-engine/manifest.js.map +1 -0
  656. package/dist/spec-engine/types.d.ts +61 -0
  657. package/dist/spec-engine/types.d.ts.map +1 -0
  658. package/dist/spec-engine/types.js +2 -0
  659. package/dist/spec-engine/types.js.map +1 -0
  660. package/dist/spec-stores/file.d.ts +12 -0
  661. package/dist/spec-stores/file.d.ts.map +1 -0
  662. package/dist/spec-stores/file.js +32 -0
  663. package/dist/spec-stores/file.js.map +1 -0
  664. package/dist/spec-stores/memory-versioned.d.ts +22 -0
  665. package/dist/spec-stores/memory-versioned.d.ts.map +1 -0
  666. package/dist/spec-stores/memory-versioned.js +125 -0
  667. package/dist/spec-stores/memory-versioned.js.map +1 -0
  668. package/dist/spec-stores/memory.d.ts +10 -0
  669. package/dist/spec-stores/memory.d.ts.map +1 -0
  670. package/dist/spec-stores/memory.js +31 -0
  671. package/dist/spec-stores/memory.js.map +1 -0
  672. package/dist/spec-stores/sqlserver-versioned.d.ts +44 -0
  673. package/dist/spec-stores/sqlserver-versioned.d.ts.map +1 -0
  674. package/dist/spec-stores/sqlserver-versioned.js +253 -0
  675. package/dist/spec-stores/sqlserver-versioned.js.map +1 -0
  676. package/dist/spec-stores/sqlserver.d.ts +27 -0
  677. package/dist/spec-stores/sqlserver.d.ts.map +1 -0
  678. package/dist/spec-stores/sqlserver.js +78 -0
  679. package/dist/spec-stores/sqlserver.js.map +1 -0
  680. package/dist/spec-stores/supabase-versioned.d.ts +38 -0
  681. package/dist/spec-stores/supabase-versioned.d.ts.map +1 -0
  682. package/dist/spec-stores/supabase-versioned.js +197 -0
  683. package/dist/spec-stores/supabase-versioned.js.map +1 -0
  684. package/dist/spec-stores/supabase.d.ts +18 -0
  685. package/dist/spec-stores/supabase.d.ts.map +1 -0
  686. package/dist/spec-stores/supabase.js +68 -0
  687. package/dist/spec-stores/supabase.js.map +1 -0
  688. package/dist/state/reserved-paths.d.ts +41 -0
  689. package/dist/state/reserved-paths.d.ts.map +1 -0
  690. package/dist/state/reserved-paths.js +40 -0
  691. package/dist/state/reserved-paths.js.map +1 -0
  692. package/dist/state/store.d.ts +18 -0
  693. package/dist/state/store.d.ts.map +1 -0
  694. package/dist/state/store.js +114 -0
  695. package/dist/state/store.js.map +1 -0
  696. package/dist/storage/supabase.d.ts +37 -0
  697. package/dist/storage/supabase.d.ts.map +1 -0
  698. package/dist/storage/supabase.js +22 -0
  699. package/dist/storage/supabase.js.map +1 -0
  700. package/dist/storage/types.d.ts +22 -0
  701. package/dist/storage/types.d.ts.map +1 -0
  702. package/dist/storage/types.js +2 -0
  703. package/dist/storage/types.js.map +1 -0
  704. package/dist/storage/upload-action.d.ts +22 -0
  705. package/dist/storage/upload-action.d.ts.map +1 -0
  706. package/dist/storage/upload-action.js +117 -0
  707. package/dist/storage/upload-action.js.map +1 -0
  708. package/dist/storage/url.d.ts +17 -0
  709. package/dist/storage/url.d.ts.map +1 -0
  710. package/dist/storage/url.js +48 -0
  711. package/dist/storage/url.js.map +1 -0
  712. package/dist/streaming/compiler.d.ts +26 -0
  713. package/dist/streaming/compiler.d.ts.map +1 -0
  714. package/dist/streaming/compiler.js +54 -0
  715. package/dist/streaming/compiler.js.map +1 -0
  716. package/dist/streaming/patch.d.ts +19 -0
  717. package/dist/streaming/patch.d.ts.map +1 -0
  718. package/dist/streaming/patch.js +139 -0
  719. package/dist/streaming/patch.js.map +1 -0
  720. package/dist/types.d.ts +443 -0
  721. package/dist/types.d.ts.map +1 -0
  722. package/dist/types.js +8 -0
  723. package/dist/types.js.map +1 -0
  724. package/dist/util/to-array.d.ts +2 -0
  725. package/dist/util/to-array.d.ts.map +1 -0
  726. package/dist/util/to-array.js +14 -0
  727. package/dist/util/to-array.js.map +1 -0
  728. package/dist/utils/levenshtein.d.ts +3 -0
  729. package/dist/utils/levenshtein.d.ts.map +1 -0
  730. package/dist/utils/levenshtein.js +29 -0
  731. package/dist/utils/levenshtein.js.map +1 -0
  732. package/dist/validation/validators.d.ts +20 -0
  733. package/dist/validation/validators.d.ts.map +1 -0
  734. package/dist/validation/validators.js +138 -0
  735. package/dist/validation/validators.js.map +1 -0
  736. package/dist/versioning/compute-patches.d.ts +7 -0
  737. package/dist/versioning/compute-patches.d.ts.map +1 -0
  738. package/dist/versioning/compute-patches.js +62 -0
  739. package/dist/versioning/compute-patches.js.map +1 -0
  740. package/dist/versioning/index.d.ts +12 -0
  741. package/dist/versioning/index.d.ts.map +1 -0
  742. package/dist/versioning/index.js +7 -0
  743. package/dist/versioning/index.js.map +1 -0
  744. package/dist/versioning/promote-gate.d.ts +12 -0
  745. package/dist/versioning/promote-gate.d.ts.map +1 -0
  746. package/dist/versioning/promote-gate.js +166 -0
  747. package/dist/versioning/promote-gate.js.map +1 -0
  748. package/dist/versioning/rollback.d.ts +26 -0
  749. package/dist/versioning/rollback.d.ts.map +1 -0
  750. package/dist/versioning/rollback.js +67 -0
  751. package/dist/versioning/rollback.js.map +1 -0
  752. package/dist/versioning/structural-diff.d.ts +8 -0
  753. package/dist/versioning/structural-diff.d.ts.map +1 -0
  754. package/dist/versioning/structural-diff.js +287 -0
  755. package/dist/versioning/structural-diff.js.map +1 -0
  756. package/dist/versioning/types.d.ts +90 -0
  757. package/dist/versioning/types.d.ts.map +1 -0
  758. package/dist/versioning/types.js +2 -0
  759. package/dist/versioning/types.js.map +1 -0
  760. package/dist/visibility/evaluator.d.ts +12 -0
  761. package/dist/visibility/evaluator.d.ts.map +1 -0
  762. package/dist/visibility/evaluator.js +76 -0
  763. package/dist/visibility/evaluator.js.map +1 -0
  764. package/dist/watchers/engine.d.ts +19 -0
  765. package/dist/watchers/engine.d.ts.map +1 -0
  766. package/dist/watchers/engine.js +24 -0
  767. package/dist/watchers/engine.js.map +1 -0
  768. package/docs/consumer/README.md +44 -0
  769. package/docs/consumer/WHERE-TO-LOOK.md +94 -0
  770. package/docs/consumer/ai-context-api.md +195 -0
  771. package/docs/consumer/ai-context-custom-elements.md +249 -0
  772. package/docs/consumer/ai-context-patterns.md +254 -0
  773. package/docs/consumer/ai-context-primitives.md +839 -0
  774. package/docs/consumer/ai-context-runtime-semantics.md +1075 -0
  775. package/docs/consumer/ai-context.md +1374 -0
  776. package/docs/consumer/reference-doc.md +3201 -0
  777. package/docs/llms.txt +17 -0
  778. package/docs/wiki/compiled/README.md +33 -0
  779. package/docs/wiki/compiled/_gaps.md +52 -0
  780. package/docs/wiki/compiled/_index.md +354 -0
  781. package/docs/wiki/compiled/_inventory.md +2986 -0
  782. package/docs/wiki/compiled/_lint.md +66 -0
  783. package/docs/wiki/compiled/_unmapped.md +3 -0
  784. package/docs/wiki/compiled/action-apply-preset.md +63 -0
  785. package/docs/wiki/compiled/action-copy-clipboard.md +45 -0
  786. package/docs/wiki/compiled/action-drawer.md +50 -0
  787. package/docs/wiki/compiled/action-export.md +66 -0
  788. package/docs/wiki/compiled/action-fetch.md +87 -0
  789. package/docs/wiki/compiled/action-form-control.md +55 -0
  790. package/docs/wiki/compiled/action-login.md +71 -0
  791. package/docs/wiki/compiled/action-modal.md +61 -0
  792. package/docs/wiki/compiled/action-navigate.md +72 -0
  793. package/docs/wiki/compiled/action-open-url.md +38 -0
  794. package/docs/wiki/compiled/action-refresh-data-source.md +51 -0
  795. package/docs/wiki/compiled/action-selection.md +55 -0
  796. package/docs/wiki/compiled/action-set-locale.md +44 -0
  797. package/docs/wiki/compiled/action-set-state.md +61 -0
  798. package/docs/wiki/compiled/action-show-notification.md +65 -0
  799. package/docs/wiki/compiled/action-submit-form.md +69 -0
  800. package/docs/wiki/compiled/action-toggle-theme.md +50 -0
  801. package/docs/wiki/compiled/action-update-tokens.md +63 -0
  802. package/docs/wiki/compiled/action-upload-file.md +87 -0
  803. package/docs/wiki/compiled/antipattern-action-chain-no-stop.md +49 -0
  804. package/docs/wiki/compiled/antipattern-all-filter-wildcard.md +59 -0
  805. package/docs/wiki/compiled/antipattern-auth-domains-port.md +47 -0
  806. package/docs/wiki/compiled/antipattern-background-blobs-prop.md +62 -0
  807. package/docs/wiki/compiled/antipattern-background-css-token.md +53 -0
  808. package/docs/wiki/compiled/antipattern-checkbox-toggle-value.md +42 -0
  809. package/docs/wiki/compiled/antipattern-crud-id-collision.md +66 -0
  810. package/docs/wiki/compiled/antipattern-derive-server-pagination.md +58 -0
  811. package/docs/wiki/compiled/antipattern-element-variant-top-level.md +57 -0
  812. package/docs/wiki/compiled/antipattern-event-expression.md +55 -0
  813. package/docs/wiki/compiled/antipattern-input-type-name.md +43 -0
  814. package/docs/wiki/compiled/antipattern-mix-fetch-and-datasources.md +48 -0
  815. package/docs/wiki/compiled/antipattern-modal-drawer-visible.md +57 -0
  816. package/docs/wiki/compiled/antipattern-motion-entrance-token.md +53 -0
  817. package/docs/wiki/compiled/antipattern-overflow-hidden-with-shadow.md +50 -0
  818. package/docs/wiki/compiled/antipattern-required-if-equals.md +59 -0
  819. package/docs/wiki/compiled/antipattern-row-literal.md +70 -0
  820. package/docs/wiki/compiled/antipattern-row-template-capture.md +42 -0
  821. package/docs/wiki/compiled/antipattern-store-save-bypass.md +72 -0
  822. package/docs/wiki/compiled/antipattern-style-block-duplication.md +56 -0
  823. package/docs/wiki/compiled/antipattern-submit-form-in-tx-confirm.md +56 -0
  824. package/docs/wiki/compiled/cli-app-spec.md +53 -0
  825. package/docs/wiki/compiled/cli-config.md +46 -0
  826. package/docs/wiki/compiled/cli-contract.md +73 -0
  827. package/docs/wiki/compiled/cli-delete.md +37 -0
  828. package/docs/wiki/compiled/cli-docs.md +44 -0
  829. package/docs/wiki/compiled/cli-elements.md +39 -0
  830. package/docs/wiki/compiled/cli-existing-spec-edit-loop.md +24 -0
  831. package/docs/wiki/compiled/cli-history.md +38 -0
  832. package/docs/wiki/compiled/cli-lint.md +77 -0
  833. package/docs/wiki/compiled/cli-manifest.md +43 -0
  834. package/docs/wiki/compiled/cli-overview.md +77 -0
  835. package/docs/wiki/compiled/cli-patch.md +85 -0
  836. package/docs/wiki/compiled/cli-programmatic-api.md +73 -0
  837. package/docs/wiki/compiled/cli-pull.md +39 -0
  838. package/docs/wiki/compiled/cli-push.md +91 -0
  839. package/docs/wiki/compiled/cli-tokens.md +38 -0
  840. package/docs/wiki/compiled/cli-toon.md +44 -0
  841. package/docs/wiki/compiled/cli-validate.md +52 -0
  842. package/docs/wiki/compiled/cli-versioning-author.md +58 -0
  843. package/docs/wiki/compiled/concept-action-chains.md +65 -0
  844. package/docs/wiki/compiled/concept-action-middleware.md +55 -0
  845. package/docs/wiki/compiled/concept-animation-build-css.md +43 -0
  846. package/docs/wiki/compiled/concept-animation-build-rn.md +44 -0
  847. package/docs/wiki/compiled/concept-animation-caps.md +38 -0
  848. package/docs/wiki/compiled/concept-animation-cascade.md +89 -0
  849. package/docs/wiki/compiled/concept-animation-null-semantics.md +55 -0
  850. package/docs/wiki/compiled/concept-animation-recipes.md +67 -0
  851. package/docs/wiki/compiled/concept-animation-triggers.md +54 -0
  852. package/docs/wiki/compiled/concept-animations-engine.md +78 -0
  853. package/docs/wiki/compiled/concept-api-audit.md +67 -0
  854. package/docs/wiki/compiled/concept-api-auth.md +67 -0
  855. package/docs/wiki/compiled/concept-api-catalogs.md +44 -0
  856. package/docs/wiki/compiled/concept-api-crud-endpoint.md +90 -0
  857. package/docs/wiki/compiled/concept-api-endpoint-properties.md +40 -0
  858. package/docs/wiki/compiled/concept-api-endpoints-overview.md +57 -0
  859. package/docs/wiki/compiled/concept-api-handler-endpoint.md +56 -0
  860. package/docs/wiki/compiled/concept-api-login-body-contract.md +47 -0
  861. package/docs/wiki/compiled/concept-api-param-properties.md +44 -0
  862. package/docs/wiki/compiled/concept-api-public-endpoint.md +33 -0
  863. package/docs/wiki/compiled/concept-api-query-endpoint.md +53 -0
  864. package/docs/wiki/compiled/concept-api-scope-filter.md +59 -0
  865. package/docs/wiki/compiled/concept-api-spec.md +70 -0
  866. package/docs/wiki/compiled/concept-app-auto-state-paths.md +59 -0
  867. package/docs/wiki/compiled/concept-app-spec.md +89 -0
  868. package/docs/wiki/compiled/concept-auth-config.md +71 -0
  869. package/docs/wiki/compiled/concept-auth-domains.md +55 -0
  870. package/docs/wiki/compiled/concept-auth-security.md +58 -0
  871. package/docs/wiki/compiled/concept-auth-state-paths.md +48 -0
  872. package/docs/wiki/compiled/concept-auto-dark-mode.md +55 -0
  873. package/docs/wiki/compiled/concept-background-caps.md +42 -0
  874. package/docs/wiki/compiled/concept-background-layer-kinds.md +80 -0
  875. package/docs/wiki/compiled/concept-background-recipes.md +53 -0
  876. package/docs/wiki/compiled/concept-background-stack.md +55 -0
  877. package/docs/wiki/compiled/concept-blob-layer.md +92 -0
  878. package/docs/wiki/compiled/concept-blob-motion.md +58 -0
  879. package/docs/wiki/compiled/concept-blob-shapes-catalog.md +45 -0
  880. package/docs/wiki/compiled/concept-cli-tokens-inspect.md +38 -0
  881. package/docs/wiki/compiled/concept-component-variants.md +89 -0
  882. package/docs/wiki/compiled/concept-cross-tab-sync.md +35 -0
  883. package/docs/wiki/compiled/concept-css-vs-motion.md +46 -0
  884. package/docs/wiki/compiled/concept-custom-detection-pattern.md +49 -0
  885. package/docs/wiki/compiled/concept-custom-element-action-props.md +81 -0
  886. package/docs/wiki/compiled/concept-custom-element-black-box.md +56 -0
  887. package/docs/wiki/compiled/concept-custom-element-cache.md +39 -0
  888. package/docs/wiki/compiled/concept-custom-element-error-boundary.md +33 -0
  889. package/docs/wiki/compiled/concept-custom-element-prop-cascade.md +60 -0
  890. package/docs/wiki/compiled/concept-custom-element-repeat.md +53 -0
  891. package/docs/wiki/compiled/concept-custom-elements.md +71 -0
  892. package/docs/wiki/compiled/concept-custom-jwt-provider.md +63 -0
  893. package/docs/wiki/compiled/concept-customize-action.md +33 -0
  894. package/docs/wiki/compiled/concept-customize-cli-lint-rule.md +30 -0
  895. package/docs/wiki/compiled/concept-customize-expression-handler.md +30 -0
  896. package/docs/wiki/compiled/concept-customize-plugin.md +43 -0
  897. package/docs/wiki/compiled/concept-customize-server-middleware.md +36 -0
  898. package/docs/wiki/compiled/concept-customize-spec-store.md +42 -0
  899. package/docs/wiki/compiled/concept-customize-validator-rule.md +35 -0
  900. package/docs/wiki/compiled/concept-customize-versioned-store.md +37 -0
  901. package/docs/wiki/compiled/concept-data-sources-lifecycle.md +61 -0
  902. package/docs/wiki/compiled/concept-data-sources.md +96 -0
  903. package/docs/wiki/compiled/concept-debugging-runtime-pointers.md +45 -0
  904. package/docs/wiki/compiled/concept-derive-datasources-mount-order.md +72 -0
  905. package/docs/wiki/compiled/concept-derive.md +67 -0
  906. package/docs/wiki/compiled/concept-dna-seeds.md +79 -0
  907. package/docs/wiki/compiled/concept-editor-commit.md +37 -0
  908. package/docs/wiki/compiled/concept-editor-save.md +18 -0
  909. package/docs/wiki/compiled/concept-editor-sessions.md +23 -0
  910. package/docs/wiki/compiled/concept-element-children-slot.md +54 -0
  911. package/docs/wiki/compiled/concept-element-definition.md +60 -0
  912. package/docs/wiki/compiled/concept-element-key.md +56 -0
  913. package/docs/wiki/compiled/concept-element-prop-definition.md +54 -0
  914. package/docs/wiki/compiled/concept-element-properties.md +81 -0
  915. package/docs/wiki/compiled/concept-element-render-node.md +62 -0
  916. package/docs/wiki/compiled/concept-element-variants.md +67 -0
  917. package/docs/wiki/compiled/concept-environment-store.md +46 -0
  918. package/docs/wiki/compiled/concept-export-action.md +56 -0
  919. package/docs/wiki/compiled/concept-expression-contexts.md +56 -0
  920. package/docs/wiki/compiled/concept-expression-timing.md +56 -0
  921. package/docs/wiki/compiled/concept-fetch-interceptors.md +49 -0
  922. package/docs/wiki/compiled/concept-file-upload-overview.md +55 -0
  923. package/docs/wiki/compiled/concept-fire-and-forget.md +45 -0
  924. package/docs/wiki/compiled/concept-font-loading.md +52 -0
  925. package/docs/wiki/compiled/concept-form-state-paths.md +55 -0
  926. package/docs/wiki/compiled/concept-forms.md +89 -0
  927. package/docs/wiki/compiled/concept-identity-accent-application.md +48 -0
  928. package/docs/wiki/compiled/concept-identity-animations.md +48 -0
  929. package/docs/wiki/compiled/concept-identity-border-elevation-override.md +56 -0
  930. package/docs/wiki/compiled/concept-identity-color-scheme.md +74 -0
  931. package/docs/wiki/compiled/concept-identity-color-weight.md +50 -0
  932. package/docs/wiki/compiled/concept-identity-glass-rn.md +46 -0
  933. package/docs/wiki/compiled/concept-identity-gradients.md +56 -0
  934. package/docs/wiki/compiled/concept-identity-heading-color.md +40 -0
  935. package/docs/wiki/compiled/concept-identity-helpers.md +61 -0
  936. package/docs/wiki/compiled/concept-identity-icons.md +46 -0
  937. package/docs/wiki/compiled/concept-identity-images.md +36 -0
  938. package/docs/wiki/compiled/concept-identity-label-style.md +36 -0
  939. package/docs/wiki/compiled/concept-identity-overview.md +72 -0
  940. package/docs/wiki/compiled/concept-identity-radius-pattern.md +39 -0
  941. package/docs/wiki/compiled/concept-identity-surface.md +62 -0
  942. package/docs/wiki/compiled/concept-identity-text-decoration.md +48 -0
  943. package/docs/wiki/compiled/concept-identity-typography-hierarchy.md +40 -0
  944. package/docs/wiki/compiled/concept-initial-actions.md +82 -0
  945. package/docs/wiki/compiled/concept-interactive-states.md +72 -0
  946. package/docs/wiki/compiled/concept-keyframe-snapshot.md +52 -0
  947. package/docs/wiki/compiled/concept-layer-background.md +80 -0
  948. package/docs/wiki/compiled/concept-motion-field.md +77 -0
  949. package/docs/wiki/compiled/concept-mount-spec-runtime.md +47 -0
  950. package/docs/wiki/compiled/concept-mythik-renderer.md +76 -0
  951. package/docs/wiki/compiled/concept-navigation-dirty-guard.md +23 -0
  952. package/docs/wiki/compiled/concept-navigation.md +75 -0
  953. package/docs/wiki/compiled/concept-package-layout.md +35 -0
  954. package/docs/wiki/compiled/concept-path-references.md +53 -0
  955. package/docs/wiki/compiled/concept-pattern-primitives.md +54 -0
  956. package/docs/wiki/compiled/concept-preset-dropdown-pattern.md +52 -0
  957. package/docs/wiki/compiled/concept-presets.md +49 -0
  958. package/docs/wiki/compiled/concept-primitive-prop-schemas.md +53 -0
  959. package/docs/wiki/compiled/concept-primitives-overview.md +49 -0
  960. package/docs/wiki/compiled/concept-promote-gate.md +47 -0
  961. package/docs/wiki/compiled/concept-prop-cascade.md +76 -0
  962. package/docs/wiki/compiled/concept-public-package-names.md +33 -0
  963. package/docs/wiki/compiled/concept-query-envelope.md +55 -0
  964. package/docs/wiki/compiled/concept-reduced-motion.md +38 -0
  965. package/docs/wiki/compiled/concept-register-presets.md +44 -0
  966. package/docs/wiki/compiled/concept-render-error-visibility.md +57 -0
  967. package/docs/wiki/compiled/concept-repeat-grouped.md +72 -0
  968. package/docs/wiki/compiled/concept-repeat-selection.md +61 -0
  969. package/docs/wiki/compiled/concept-repeat.md +81 -0
  970. package/docs/wiki/compiled/concept-role-access.md +61 -0
  971. package/docs/wiki/compiled/concept-rollback.md +36 -0
  972. package/docs/wiki/compiled/concept-rules-catalog.md +49 -0
  973. package/docs/wiki/compiled/concept-screen-definition.md +51 -0
  974. package/docs/wiki/compiled/concept-screen-outlet.md +46 -0
  975. package/docs/wiki/compiled/concept-session-persistence.md +41 -0
  976. package/docs/wiki/compiled/concept-shape-animations.md +66 -0
  977. package/docs/wiki/compiled/concept-skeleton-auto.md +58 -0
  978. package/docs/wiki/compiled/concept-skeleton-manual.md +47 -0
  979. package/docs/wiki/compiled/concept-source-of-truth-references.md +37 -0
  980. package/docs/wiki/compiled/concept-source-reading-misleading.md +37 -0
  981. package/docs/wiki/compiled/concept-spatial-map-editor.md +22 -0
  982. package/docs/wiki/compiled/concept-spatial-map-zones.md +18 -0
  983. package/docs/wiki/compiled/concept-spec-engine.md +47 -0
  984. package/docs/wiki/compiled/concept-spec-store-interface.md +59 -0
  985. package/docs/wiki/compiled/concept-spec-store-layering.md +51 -0
  986. package/docs/wiki/compiled/concept-spec-stores-catalog.md +66 -0
  987. package/docs/wiki/compiled/concept-spec-structure.md +94 -0
  988. package/docs/wiki/compiled/concept-spec-types.md +76 -0
  989. package/docs/wiki/compiled/concept-state-change-animation.md +73 -0
  990. package/docs/wiki/compiled/concept-state-policies.md +44 -0
  991. package/docs/wiki/compiled/concept-state-protection.md +51 -0
  992. package/docs/wiki/compiled/concept-storage-adapter.md +69 -0
  993. package/docs/wiki/compiled/concept-storage-custom-names.md +69 -0
  994. package/docs/wiki/compiled/concept-storage-evolution.md +32 -0
  995. package/docs/wiki/compiled/concept-storage-idempotency.md +35 -0
  996. package/docs/wiki/compiled/concept-storage-overview.md +81 -0
  997. package/docs/wiki/compiled/concept-storage-postgres-jsonb.md +53 -0
  998. package/docs/wiki/compiled/concept-storage-postgres-triggers.md +60 -0
  999. package/docs/wiki/compiled/concept-storage-table-environments.md +40 -0
  1000. package/docs/wiki/compiled/concept-storage-table-screens.md +44 -0
  1001. package/docs/wiki/compiled/concept-storage-table-versions.md +50 -0
  1002. package/docs/wiki/compiled/concept-storage-verification.md +57 -0
  1003. package/docs/wiki/compiled/concept-template-children-marker.md +56 -0
  1004. package/docs/wiki/compiled/concept-template-interpolation.md +60 -0
  1005. package/docs/wiki/compiled/concept-templates-vs-variants.md +80 -0
  1006. package/docs/wiki/compiled/concept-templates.md +93 -0
  1007. package/docs/wiki/compiled/concept-token-categories.md +58 -0
  1008. package/docs/wiki/compiled/concept-token-system.md +61 -0
  1009. package/docs/wiki/compiled/concept-transaction-phase-timing.md +47 -0
  1010. package/docs/wiki/compiled/concept-transaction-rollback.md +38 -0
  1011. package/docs/wiki/compiled/concept-transaction-snapshot.md +37 -0
  1012. package/docs/wiki/compiled/concept-transactions.md +72 -0
  1013. package/docs/wiki/compiled/concept-validation-checks.md +63 -0
  1014. package/docs/wiki/compiled/concept-validators-catalog.md +76 -0
  1015. package/docs/wiki/compiled/concept-versioned-store.md +65 -0
  1016. package/docs/wiki/compiled/concept-versioning-snapshots-patches.md +61 -0
  1017. package/docs/wiki/compiled/concept-visibility.md +55 -0
  1018. package/docs/wiki/compiled/concept-web-only-recipes.md +37 -0
  1019. package/docs/wiki/compiled/concept-where-to-look.md +78 -0
  1020. package/docs/wiki/compiled/expression-and-or-not.md +68 -0
  1021. package/docs/wiki/compiled/expression-array.md +111 -0
  1022. package/docs/wiki/compiled/expression-auth.md +83 -0
  1023. package/docs/wiki/compiled/expression-binditem.md +52 -0
  1024. package/docs/wiki/compiled/expression-bindstate.md +63 -0
  1025. package/docs/wiki/compiled/expression-breakpoint.md +71 -0
  1026. package/docs/wiki/compiled/expression-computed.md +52 -0
  1027. package/docs/wiki/compiled/expression-cond.md +78 -0
  1028. package/docs/wiki/compiled/expression-date.md +66 -0
  1029. package/docs/wiki/compiled/expression-format.md +79 -0
  1030. package/docs/wiki/compiled/expression-group.md +84 -0
  1031. package/docs/wiki/compiled/expression-i18n.md +53 -0
  1032. package/docs/wiki/compiled/expression-item-index.md +72 -0
  1033. package/docs/wiki/compiled/expression-let-ref.md +87 -0
  1034. package/docs/wiki/compiled/expression-math.md +67 -0
  1035. package/docs/wiki/compiled/expression-platform.md +68 -0
  1036. package/docs/wiki/compiled/expression-prop.md +89 -0
  1037. package/docs/wiki/compiled/expression-selection.md +58 -0
  1038. package/docs/wiki/compiled/expression-state.md +70 -0
  1039. package/docs/wiki/compiled/expression-switch.md +68 -0
  1040. package/docs/wiki/compiled/expression-template.md +71 -0
  1041. package/docs/wiki/compiled/expression-token.md +67 -0
  1042. package/docs/wiki/compiled/path-app-screens.md +55 -0
  1043. package/docs/wiki/compiled/path-data-source-paths.md +44 -0
  1044. package/docs/wiki/compiled/path-forms.md +38 -0
  1045. package/docs/wiki/compiled/path-login.md +57 -0
  1046. package/docs/wiki/compiled/path-navigation.md +62 -0
  1047. package/docs/wiki/compiled/path-presets-available.md +44 -0
  1048. package/docs/wiki/compiled/path-tokens.md +56 -0
  1049. package/docs/wiki/compiled/path-tx-result-error.md +56 -0
  1050. package/docs/wiki/compiled/path-ui-device.md +53 -0
  1051. package/docs/wiki/compiled/path-ui-loading-error.md +40 -0
  1052. package/docs/wiki/compiled/path-ui-modals-drawers.md +46 -0
  1053. package/docs/wiki/compiled/path-ui-selected-row.md +92 -0
  1054. package/docs/wiki/compiled/path-uploads.md +32 -0
  1055. package/docs/wiki/compiled/pattern-cross-screen-data-flow.md +83 -0
  1056. package/docs/wiki/compiled/pattern-fetch-vs-datasources.md +63 -0
  1057. package/docs/wiki/compiled/pattern-file-upload-auto.md +61 -0
  1058. package/docs/wiki/compiled/pattern-file-upload-manual.md +59 -0
  1059. package/docs/wiki/compiled/pattern-form-validation-cross-field.md +58 -0
  1060. package/docs/wiki/compiled/pattern-form-validation.md +58 -0
  1061. package/docs/wiki/compiled/pattern-fullstack-coherence.md +49 -0
  1062. package/docs/wiki/compiled/pattern-git-vs-db-versioning.md +55 -0
  1063. package/docs/wiki/compiled/pattern-identity-aware-spec.md +70 -0
  1064. package/docs/wiki/compiled/pattern-loading-content-empty.md +66 -0
  1065. package/docs/wiki/compiled/pattern-login-body-template.md +47 -0
  1066. package/docs/wiki/compiled/pattern-login-screen.md +83 -0
  1067. package/docs/wiki/compiled/pattern-push-vs-patch.md +54 -0
  1068. package/docs/wiki/compiled/pattern-reusable-components.md +67 -0
  1069. package/docs/wiki/compiled/pattern-tx-create.md +76 -0
  1070. package/docs/wiki/compiled/pattern-tx-delete.md +50 -0
  1071. package/docs/wiki/compiled/pattern-tx-toggle.md +45 -0
  1072. package/docs/wiki/compiled/pattern-tx-update.md +68 -0
  1073. package/docs/wiki/compiled/primitive-accordion.md +46 -0
  1074. package/docs/wiki/compiled/primitive-area-chart.md +35 -0
  1075. package/docs/wiki/compiled/primitive-audio-player.md +27 -0
  1076. package/docs/wiki/compiled/primitive-bar-chart.md +34 -0
  1077. package/docs/wiki/compiled/primitive-box.md +58 -0
  1078. package/docs/wiki/compiled/primitive-button.md +55 -0
  1079. package/docs/wiki/compiled/primitive-camera.md +39 -0
  1080. package/docs/wiki/compiled/primitive-checkbox.md +62 -0
  1081. package/docs/wiki/compiled/primitive-divider.md +31 -0
  1082. package/docs/wiki/compiled/primitive-drawer.md +44 -0
  1083. package/docs/wiki/compiled/primitive-file-upload.md +65 -0
  1084. package/docs/wiki/compiled/primitive-grid.md +48 -0
  1085. package/docs/wiki/compiled/primitive-icon.md +106 -0
  1086. package/docs/wiki/compiled/primitive-image.md +49 -0
  1087. package/docs/wiki/compiled/primitive-input.md +81 -0
  1088. package/docs/wiki/compiled/primitive-kanban-board.md +30 -0
  1089. package/docs/wiki/compiled/primitive-line-chart.md +33 -0
  1090. package/docs/wiki/compiled/primitive-list.md +34 -0
  1091. package/docs/wiki/compiled/primitive-modal.md +51 -0
  1092. package/docs/wiki/compiled/primitive-pie-chart.md +32 -0
  1093. package/docs/wiki/compiled/primitive-screen-outlet.md +42 -0
  1094. package/docs/wiki/compiled/primitive-screen.md +43 -0
  1095. package/docs/wiki/compiled/primitive-scroll.md +33 -0
  1096. package/docs/wiki/compiled/primitive-select.md +76 -0
  1097. package/docs/wiki/compiled/primitive-signature.md +40 -0
  1098. package/docs/wiki/compiled/primitive-skeleton.md +48 -0
  1099. package/docs/wiki/compiled/primitive-slider.md +47 -0
  1100. package/docs/wiki/compiled/primitive-spacer.md +33 -0
  1101. package/docs/wiki/compiled/primitive-spatial-map.md +26 -0
  1102. package/docs/wiki/compiled/primitive-stack.md +46 -0
  1103. package/docs/wiki/compiled/primitive-table.md +127 -0
  1104. package/docs/wiki/compiled/primitive-tabs.md +52 -0
  1105. package/docs/wiki/compiled/primitive-text.md +59 -0
  1106. package/docs/wiki/compiled/primitive-textarea.md +44 -0
  1107. package/docs/wiki/compiled/primitive-toast-container.md +44 -0
  1108. package/docs/wiki/compiled/primitive-toggle.md +46 -0
  1109. package/docs/wiki/compiled/primitive-touchable.md +40 -0
  1110. package/docs/wiki/compiled/primitive-wizard.md +43 -0
  1111. package/package.json +58 -8
@@ -0,0 +1,3201 @@
1
+ # Mythik — Reference Document
2
+
3
+ > The AI reads this to produce correct JSON specs.
4
+ > Every element type, expression, action, and pattern available is documented here with examples.
5
+
6
+ ---
7
+
8
+ ## How to Use This Document
9
+
10
+ When asked to create or modify UI, produce a JSON **Spec** following the structure and rules below.
11
+
12
+ ## CLI — Spec Modification via Mythik CLI
13
+
14
+ Use the `mythik` CLI to read and modify specs stored in Supabase (or other stores).
15
+
16
+ For AI agents, locate the bundled Mythik documentation before generating or changing specs:
17
+
18
+ ```bash
19
+ mythik docs path
20
+ mythik docs copy ./mythik-docs
21
+ ```
22
+
23
+ The `mythik` npm package ships `docs/llms.txt`, canonical `docs/consumer/*`, and the compiled `docs/wiki/compiled/*` wiki so the framework contract is available after install.
24
+
25
+ ### Commands
26
+
27
+ ```bash
28
+ # Initialize configuration
29
+ mythik init --store supabase --url <url> --key $MYTHIK_API_KEY
30
+
31
+ # See the structural tree of a screen (colorized output)
32
+ mythik manifest <screen>
33
+
34
+ # Get specific element details
35
+ mythik elements <screen> <id1,id2,...>
36
+
37
+ # Apply RFC 6902 patches
38
+ mythik patch <screen> --from-file patch.json
39
+
40
+ # Export a full screen spec for backup/review
41
+ mythik pull <screen>
42
+
43
+ # Create or intentionally replace a full screen spec from stdin
44
+ mythik push <screen>
45
+
46
+ # Validate a screen spec
47
+ mythik validate <screen>
48
+
49
+ # Delete a screen (preview without --confirm)
50
+ mythik delete <screen> --confirm
51
+ ```
52
+
53
+ All commands accept `--json` for machine-parseable output and `--store`, `--url`, `--key` flags (or read from `.mythikrc`).
54
+
55
+ ### Configuration — Never pass keys inline
56
+
57
+ **Always** use `.mythikrc` + environment variables. Never pass API keys as CLI flags — they appear in terminal history, chat logs, and process lists.
58
+
59
+ ```json
60
+ // .mythikrc
61
+ {
62
+ "store": "supabase",
63
+ "supabase": {
64
+ "url": "https://xxx.supabase.co",
65
+ "apiKey": "$MYTHIK_API_KEY"
66
+ }
67
+ }
68
+ ```
69
+
70
+ Set the env var once per session: `export MYTHIK_API_KEY="your-key"`. The CLI resolves `$MYTHIK_API_KEY` at runtime. Add `.mythikrc` to `.gitignore`.
71
+
72
+ ### Patch Input: File vs Stdin
73
+
74
+ **Preferred shell-safe path** - write patch JSON to a file:
75
+ ```bash
76
+ mythik patch task-manager --from-file patch.json
77
+ ```
78
+
79
+ `--from-file <path>` wins over ambient non-TTY stdin. Use `--from-file -` or pipe without `--from-file` when stdin is intentional:
80
+ ```bash
81
+ cat patch.json | mythik patch task-manager
82
+ mythik patch task-manager --from-file -
83
+ ```
84
+
85
+ Inline JSON still works for tiny patches, but `--from-file` is preferred for anything containing `$state`, `$template`, `$auth`, or shell-sensitive quoting.
86
+
87
+ **Array insertion:** `"op": "add"` with a numeric index (e.g., `"/elements/form/children/2"`) **inserts before** that index — it does NOT replace. Use `/-` to append at the end. Use `"op": "replace"` if you want to overwrite.
88
+
89
+ ### TOON Format (Token-Efficient Output)
90
+
91
+ Use `--toon` for token-efficient output on `elements` and `patch` commands:
92
+
93
+ ```bash
94
+ mythik elements task-manager btn,nav --toon
95
+ mythik patch task-manager --from-file patch.toon --toon
96
+ ```
97
+
98
+ TOON reduces output by ~40% vs JSON. Lossless roundtrip. Based on `@toon-format/toon` v2.
99
+
100
+ **Patch input autodetects format** — JSON (starts with `[` or `{`) or TOON (everything else). You can send patches in either format:
101
+
102
+ ```bash
103
+ # JSON input (as before)
104
+ mythik patch task-manager --from-file patch.json
105
+
106
+ # TOON input (autodetected)
107
+ mythik patch task-manager --from-file patch.toon
108
+ ```
109
+
110
+ ### Workflow: Manifest → Elements → Patch
111
+
112
+ 1. **`mythik manifest <screen>`** — see the structure, decide what to modify
113
+ 2. **`mythik elements <screen> <ids>`** — inspect specific elements you need to change
114
+ 3. **`mythik patch <screen> --from-file patch.json`** - apply surgical changes
115
+ 4. **`mythik manifest <screen>`** — verify the result
116
+
117
+ This is the required default loop for AI agents editing an existing spec. Do not begin a small change by pulling the full spec and rewriting it. `manifest` gives the structural map, `elements` gives the exact JSON for the target nodes, and `patch --from-file` keeps the write validated, versionable, and reviewable. Use `pull` for backup/migration/review and `push` for new specs or deliberate full replacement only.
118
+
119
+ ### Full Spec Lifecycle
120
+
121
+ ```bash
122
+ Create: mythik push <id> < spec.json
123
+ Read: mythik manifest <id> / mythik elements <id> ids / mythik pull <id>
124
+ Modify: mythik patch <id> --from-file patch.json
125
+ Validate: mythik validate <id>
126
+ Delete: mythik delete <id> --confirm
127
+ Backup: mythik pull <id> > backup.json
128
+ Restore: mythik push <id> < backup.json
129
+ Migrate: mythik pull <id> --store A | mythik push <id> --store B
130
+ Tokens: mythik tokens --dna '{"primary":"#0D9488"}' --json
131
+ ```
132
+
133
+ ### Push — Create New Screens
134
+
135
+ Write a JSON spec and push via stdin. No seed scripts, no `.ts` files:
136
+
137
+ ```bash
138
+ # From a file
139
+ mythik push login < login-spec.json
140
+
141
+ # Inline with heredoc
142
+ mythik push login <<'EOF'
143
+ { "root": "page", "elements": { "page": { "type": "box", "children": ["title"] }, "title": { "type": "text", "props": { "content": "Login" } } } }
144
+ EOF
145
+ ```
146
+
147
+ **New screens** save even with validation errors (fix with `mythik patch`). **Existing screens** reject invalid specs unless `--force` is used. Validation errors include **suggested fix patches** for auto-fixable issues (typos, orphan children).
148
+
149
+ ### Delete — With Safety Gate
150
+
151
+ ```bash
152
+ # Preview (no deletion)
153
+ mythik delete task-manager
154
+ # → Screen "task-manager" (107 elements). Use --confirm to delete.
155
+
156
+ # Delete + backup in one command
157
+ mythik delete task-manager --confirm > backup.json
158
+ ```
159
+
160
+ ### AppSpec — App-Level Configuration
161
+
162
+ The same CLI commands work on AppSpec documents (`type: "app"`). Detection is automatic.
163
+
164
+ ```bash
165
+ # View app structure
166
+ mythik manifest app-demo
167
+
168
+ # Inspect specific sections (dot-notation)
169
+ mythik elements app-demo tokens.colors
170
+ mythik elements app-demo screens.task-manager
171
+ mythik elements app-demo navigation.auth
172
+
173
+ # Inspect layout elements (simple ID)
174
+ mythik elements app-demo sidebar,nav-item
175
+
176
+ # Mix both
177
+ mythik elements app-demo sidebar,tokens.colors,screens.task-manager
178
+
179
+ # Patch tokens
180
+ mythik patch app-demo '[{"op":"replace","path":"/tokens/colors/primary","value":"#2563EB"}]'
181
+
182
+ # Patch layout elements
183
+ mythik patch app-demo '[{"op":"replace","path":"/layout/elements/sidebar/props/style/width","value":280}]'
184
+
185
+ # Add a new screen
186
+ mythik patch app-demo '[{"op":"add","path":"/screens/reports","value":{"label":"Reports","icon":"chart-bar"}}]'
187
+
188
+ # Validate
189
+ mythik validate app-demo
190
+ ```
191
+
192
+ **Key differences from screen specs:**
193
+ - Elements live under `/layout/elements/` (not `/elements/`)
194
+ - Dot-notation in `elements` command for inspecting tokens, screens, navigation, translations, sharedState
195
+ - Patch paths use full JSON Pointer: `/tokens/...`, `/screens/...`, `/layout/elements/...`
196
+ - Validation includes cross-reference checks (screen existence, roleAccess consistency)
197
+ - Translation key mismatches are warnings, not errors
198
+
199
+ ---
200
+
201
+ ## Spec Structure (Flat Tree)
202
+
203
+ Every screen is a JSON Spec with a `root` ID, a flat `elements` map, and optional `initialActions`:
204
+
205
+ ```json
206
+ {
207
+ "root": "main-layout",
208
+ "initialActions": [
209
+ { "action": "fetch", "params": { "url": "https://api.example.com/data", "target": "/items" } }
210
+ ],
211
+ "elements": {
212
+ "main-layout": {
213
+ "type": "stack",
214
+ "props": { "direction": "vertical" },
215
+ "children": ["header", "content"]
216
+ },
217
+ "header": {
218
+ "type": "text",
219
+ "props": { "content": { "$template": "${/patient/name}" }, "variant": "heading" }
220
+ },
221
+ "content": {
222
+ "type": "box",
223
+ "props": { "style": { "padding": { "$token": "spacing.unit", "multiply": 3 } } },
224
+ "children": ["field1", "field2"]
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ ### Top-Level Spec Properties
231
+
232
+ | Property | Type | Purpose |
233
+ |----------|------|---------|
234
+ | `root` | string | ID of the root element |
235
+ | `elements` | object | Flat map of element ID → element definition |
236
+ | `initialActions` | ActionBinding[] | Actions to execute when the spec mounts (e.g., fetch data) |
237
+
238
+ ### Element Properties
239
+
240
+ | Property | Type | Purpose |
241
+ |----------|------|---------|
242
+ | `type` | string | Primitive or custom element name |
243
+ | `props` | object | Component props (can contain expressions) |
244
+ | `children` | string[] | IDs of child elements |
245
+ | `style` | object | CSS styles (can contain expressions) |
246
+ | `visible` | boolean or condition | Show/hide condition |
247
+ | `permission` | object | Role-based access: `{ visible, editable, readonly }` |
248
+ | `repeat` | object | Iterate over array: `{ statePath, key }` or `{ source: expression, key }` |
249
+ | `on` | object | Event handlers: `{ press: action, change: action }` |
250
+ | `hover` | object | Style overrides when pointer enters (rendered via Motion) |
251
+ | `active` | object | Style overrides when element is pressed (rendered via Motion) |
252
+ | `focus` | object | Style overrides when element has keyboard focus (rendered via Motion) |
253
+ | `transition` | object | Animation config: `{ duration, ease, delay, type, stiffness, damping }` |
254
+ | `motion` | object | Mount/exit animations: `{ initial, animate, exit, transition }` |
255
+ | `key` | expression | Dynamic React key — forces remount when value changes. Supports `$template`, `$state`. Use to re-trigger mount animations on state change |
256
+ | `skeleton` | `false` | Opt out of auto-skeleton for this element |
257
+
258
+ ---
259
+
260
+ ## Expression Types
261
+
262
+ Expressions are special JSON objects that resolve to dynamic values at runtime.
263
+
264
+ ### `$state` — Read from state
265
+ ```json
266
+ { "$state": "/user/name" }
267
+ ```
268
+ Reads the value at the given JSON Pointer path from application state.
269
+
270
+ ### `$bindState` — Two-way binding
271
+ ```json
272
+ { "$bindState": "/form/email" }
273
+ ```
274
+ Reads AND writes. When the user changes the input, the state updates automatically.
275
+
276
+ ### `$token` — Design system token
277
+ ```json
278
+ { "$token": "colors.primary" }
279
+ { "$token": "spacing.unit", "multiply": 3 }
280
+ ```
281
+ Resolves a value from the design system. Use dot notation. `multiply` scales numeric tokens.
282
+
283
+ **Dynamic theme:** `$token` automatically reads `/preferences/theme` from state. When the theme is "dark" and dark mode tokens are configured, `$token` resolves with the dark mode overrides. No manual switching needed — use `toggleTheme` action and tokens resolve correctly.
284
+
285
+ ### `$cond` / `$then` / `$else` — Conditional value
286
+ ```json
287
+ {
288
+ "$cond": { "$state": "/form/isValid" },
289
+ "$then": { "$token": "colors.success" },
290
+ "$else": { "$token": "colors.muted" }
291
+ }
292
+ ```
293
+ Evaluates a condition. Supports operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `not`.
294
+
295
+ Nested conditionals for multi-branch logic:
296
+ ```json
297
+ {
298
+ "$cond": { "$item": "priority", "eq": "urgent" },
299
+ "$then": "#EF4444",
300
+ "$else": {
301
+ "$cond": { "$item": "priority", "eq": "high" },
302
+ "$then": "#F97316",
303
+ "$else": "#22C55E"
304
+ }
305
+ }
306
+ ```
307
+
308
+ ### `$template` — String interpolation
309
+ ```json
310
+ { "$template": "Hello, ${/user/name}! You have ${/inbox/count} messages." }
311
+ ```
312
+ Interpolates values into a string. Supports two reference types:
313
+ - State paths: `${/user/name}` — reads from application state
314
+ - `$let` bindings: `${age}` — reads from a named binding defined with `$let`
315
+
316
+ ### `$computed` — Registered function
317
+ ```json
318
+ { "$computed": "calculateBMI", "args": { "weight": { "$state": "/patient/weight" }, "height": { "$state": "/patient/height" } } }
319
+ ```
320
+ Calls a registered function with resolved arguments. **Prefer `$math`, `$array`, `$date`, `$format` over `$computed`** — built-in operations cover 90%+ of cases without code.
321
+
322
+ ### `$let` / `$ref` — Named bindings
323
+ ```json
324
+ {
325
+ "$let": { "bmi": { "$computed": "calculateBMI", "args": { ... } } },
326
+ "$in": { "$ref": "bmi" }
327
+ }
328
+ ```
329
+ Define a named value once, reference it multiple times. Avoids repetition.
330
+
331
+ **Later bindings can reference earlier ones:**
332
+ ```json
333
+ {
334
+ "$let": {
335
+ "total": { "$array": "count", "source": { "$state": "/items" } },
336
+ "label": { "$template": "${total} items" }
337
+ },
338
+ "$in": { "$ref": "label" }
339
+ }
340
+ ```
341
+
342
+ **IMPORTANT — JSONB array format:** When the spec is stored in a database (JSONB), key ordering is not preserved. If bindings reference each other via `$ref`, use array format to guarantee order:
343
+ ```json
344
+ {
345
+ "$let": [
346
+ ["filtered", { "$array": "filter", "source": { "$state": "/items" }, "where": { "field": "status", "eq": "active" } }],
347
+ ["count", { "$array": "count", "source": { "$ref": "filtered" } }]
348
+ ],
349
+ "$in": { "$template": "${count} active items" }
350
+ }
351
+ ```
352
+ Object format is fine when bindings don't reference each other.
353
+
354
+ ### `$i18n` — Translation
355
+ ```json
356
+ { "$i18n": "patient.name" }
357
+ { "$i18n": "welcome", "args": { "name": { "$state": "/user/name" } } }
358
+ ```
359
+ Resolves a translation key for the active locale.
360
+
361
+ ### `$breakpoint` — Responsive value
362
+ ```json
363
+ { "$breakpoint": { "sm": 1, "md": 2, "lg": 3 } }
364
+ ```
365
+ Resolves to different values based on viewport width. **Auto-tracked** — the renderer writes `/ui/device/viewportWidth` on mount and on resize. No manual setup needed.
366
+
367
+ Default breakpoints: `sm: 0`, `md: 768`, `lg: 1024`, `xl: 1280`. Override via design tokens.
368
+
369
+ ### `$platform` — Cross-platform value
370
+ ```json
371
+ { "$platform": { "web": "blur(24px)", "native": null } }
372
+ ```
373
+ Resolves to different values based on the current platform (`web`, `ios`, `android`). Reads `/ui/device/platform` from state — set automatically by `useDeviceContext` on both web and React Native.
374
+
375
+ **`native` alias** matches any non-web platform (ios, android). Specific platform keys take priority over `native`:
376
+ ```json
377
+ { "$platform": { "web": "layout-web", "native": "layout-native" } }
378
+ { "$platform": { "web": "hover-card", "ios": "ios-card", "android": "android-card" } }
379
+ ```
380
+
381
+ **Use with `$breakpoint`** — `$breakpoint` handles responsive layout (viewport width), `$platform` handles platform-specific techniques (CSS vs native components). They are complementary, not alternatives.
382
+
383
+ ### `$item` / `$index` — Repeat context (read-only)
384
+ ```json
385
+ { "$item": "title" }
386
+ { "$item": "" }
387
+ { "$index": true }
388
+ ```
389
+ Inside a `repeat` block: `$item` reads a field from the current item, `$index` returns the position. Can be used in `visible`, `style`, `props`, and `on` (event params are pre-resolved with item context).
390
+
391
+ ### `$bindItem` — Repeat context (two-way)
392
+ ```json
393
+ { "$bindItem": "completed" }
394
+ ```
395
+ Two-way binding to a field in the current repeat item.
396
+
397
+ ### `$prop` — Element prop reference (Layer 3)
398
+ ```json
399
+ { "$prop": "label" }
400
+ ```
401
+ References a prop passed to a custom element definition. Used inside element render trees.
402
+
403
+ ### `$math` — Arithmetic operations
404
+ ```json
405
+ { "$math": "add", "args": [{ "$state": "/a" }, { "$state": "/b" }] }
406
+ { "$math": "multiply", "args": [{ "$state": "/price" }, { "$state": "/qty" }] }
407
+ { "$math": "round", "value": 3.14159, "decimals": 2 }
408
+ ```
409
+ Operations: `add`, `subtract`, `multiply`, `divide`, `round`, `floor`, `ceil`, `abs`, `min`, `max`, `mod`
410
+
411
+ **Undefined args → 0:** If a `$math` argument resolves to `undefined` or `NaN` (e.g., reading a state path that doesn't exist yet), it is treated as `0`. This prevents NaN from propagating through the UI. For calculations that depend on data being loaded (e.g., `multiply(price, quantity)`), ensure the data exists before rendering — use `visible` conditions or `initialActions` to load data first. The spec author is responsible for data availability; `$math` is responsible for never producing NaN.
412
+
413
+ ### `$array` — Array operations
414
+ ```json
415
+ { "$array": "count", "source": { "$state": "/products" } }
416
+ { "$array": "count", "source": { "$state": "/products" }, "where": { "field": "stock", "lt": 10 } }
417
+ { "$array": "sum", "source": { "$state": "/products" }, "field": "price" }
418
+ { "$array": "sumProduct", "source": { "$state": "/products" }, "field1": "price", "field2": "stock" }
419
+ { "$array": "filter", "source": { "$state": "/items" }, "where": { "field": "status", "eq": "active" } }
420
+ { "$array": "remove", "source": { "$state": "/items" }, "where": { "field": "id", "eq": 3 } }
421
+ { "$array": "append", "source": { "$state": "/items" }, "value": { "name": "New Item" } }
422
+ { "$array": "replace", "source": { "$state": "/items" }, "where": { "field": "id", "eq": 1 }, "value": { "name": "Updated" } }
423
+ { "$array": "toggle", "source": { "$state": "/selectedIds" }, "value": 5 }
424
+ { "$array": "search", "source": { "$state": "/products" }, "query": { "$state": "/search" }, "fields": ["name", "category"] }
425
+ { "$array": "slice", "source": { "$state": "/items" }, "from": 0, "to": 10 }
426
+ { "$array": "sort", "source": { "$state": "/items" }, "field": "name", "direction": "asc" }
427
+ { "$array": "find", "source": { "$state": "/items" }, "where": { "field": "id", "eq": 1 } }
428
+ { "$array": "includes", "source": { "$state": "/tags" }, "value": "urgent" }
429
+ { "$array": "map", "source": { "$state": "/items" }, "field": "name" }
430
+ { "$array": "first", "source": { "$state": "/items" } }
431
+ { "$array": "last", "source": { "$state": "/items" } }
432
+ ```
433
+ Where operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`
434
+
435
+ ### `$date` — Date operations
436
+ ```json
437
+ { "$date": "now" }
438
+ { "$date": "today" }
439
+ { "$date": "age", "from": { "$state": "/birthDate" } }
440
+ { "$date": "diff", "from": { "$state": "/start" }, "to": { "$state": "/end" }, "unit": "days" }
441
+ { "$date": "format", "value": { "$state": "/date" }, "pattern": "short" }
442
+ { "$date": "add", "value": { "$state": "/date" }, "amount": 7, "unit": "days" }
443
+ ```
444
+ Units: `days`, `months`, `years`, `hours`, `minutes`. Patterns: `short`, `long`, `numeric`, `time`, `datetime`
445
+
446
+ ### `$format` — Value formatting
447
+ ```json
448
+ { "$format": "currency", "value": { "$state": "/price" }, "currency": "USD" }
449
+ { "$format": "currency", "value": 1234, "currency": "HNL", "locale": "es-HN" }
450
+ { "$format": "number", "value": 1234.5, "decimals": 2 }
451
+ { "$format": "number", "value": 1234567, "notation": "compact" }
452
+ { "$format": "number", "value": -500, "signDisplay": "always", "decimals": 2 }
453
+ { "$format": "percent", "value": 0.75 }
454
+ { "$format": "phone", "value": { "$state": "/phone" } }
455
+ { "$format": "uppercase", "value": { "$state": "/name" } }
456
+ { "$format": "lowercase", "value": "HELLO" }
457
+ { "$format": "capitalize", "value": "hello world" }
458
+ { "$format": "truncate", "value": "long text...", "length": 20 }
459
+ ```
460
+
461
+ **Extended options for `currency` and `number`:**
462
+
463
+ | Parameter | Type | Default | Description |
464
+ |-----------|------|---------|-------------|
465
+ | `locale` | string or Expression | context locale | Override locale per-field (e.g., `"es-HN"`) |
466
+ | `notation` | `"standard"` \| `"compact"` \| `"scientific"` \| `"engineering"` | `"standard"` | Number notation style |
467
+ | `signDisplay` | `"auto"` \| `"always"` \| `"exceptZero"` \| `"negative"` | `"auto"` | Sign display mode |
468
+ | `useGrouping` | boolean | `true` | Thousands separator on/off |
469
+
470
+ All parameters are optional — existing specs work unchanged.
471
+
472
+ ### `$and` / `$or` / `$not` — Boolean logic
473
+ ```json
474
+ { "$and": [{ "$state": "/form/name" }, { "$state": "/form/price" }] }
475
+ { "$or": [{ "$state": "/user/isAdmin" }, { "$state": "/user/isModerator" }] }
476
+ { "$not": { "$state": "/form/isValid" } }
477
+ ```
478
+ Works everywhere — `visible`, `disabled`, `style`, any prop. Returns true/false.
479
+
480
+ ### `$switch` — Multi-branch conditional
481
+ ```json
482
+ {
483
+ "$switch": { "$state": "/filter/type" },
484
+ "cases": {
485
+ "1": "Current Expenses",
486
+ "2": "Capital Expenses",
487
+ "3": { "$token": "colors.warning" },
488
+ "4": { "$template": "Type ${/count}" }
489
+ },
490
+ "default": "Other"
491
+ }
492
+ ```
493
+ Evaluates `$switch` value, converts to string, looks up in `cases`. Only the matching case is resolved (lazy). `default` is required — prevents silent `undefined`.
494
+
495
+ Replaces deeply nested `$cond` for 3+ branches. Cases can contain any expression.
496
+
497
+ ### `$group` — Group context (inside repeat.groupBy)
498
+ ```json
499
+ { "$group": "key" }
500
+ { "$group": "count" }
501
+ { "$group": "index" }
502
+ { "$group": "items" }
503
+ { "$group": "sum", "field": "amount" }
504
+ { "$group": "avg", "field": "price" }
505
+ { "$group": "min", "field": "score" }
506
+ { "$group": "max", "field": "score" }
507
+ { "$group": "subtotal.total" }
508
+ ```
509
+
510
+ | Operation | Description | Available in |
511
+ |-----------|-------------|-------------|
512
+ | `key` | Group key value (e.g., "Institution A") | groupHeader, groupFooter |
513
+ | `count` | Number of items in this group | groupHeader, groupFooter |
514
+ | `index` | Group index (0-based) | groupHeader, groupFooter |
515
+ | `items` | Array of items in this group | groupHeader, groupFooter |
516
+ | `sum` | Sum of `field` across group items | groupHeader, groupFooter |
517
+ | `avg` | Average of `field` | groupHeader, groupFooter |
518
+ | `min` | Minimum of `field` | groupHeader, groupFooter |
519
+ | `max` | Maximum of `field` | groupHeader, groupFooter |
520
+ | `dot.path` | Dot-notation access to raw group object (pre-grouped mode) | groupHeader, groupFooter |
521
+
522
+ **Only valid inside `repeat` with `groupBy` or `groupKey`.** Throws error if used outside.
523
+
524
+ ### `$selection` — Selection state (inside repeat.selection)
525
+ ```json
526
+ { "$selection": "selected" }
527
+ { "$selection": "count" }
528
+ ```
529
+ | Operation | Returns | Description |
530
+ |-----------|---------|-------------|
531
+ | `selected` | boolean | Whether the current item's key is in the selection array |
532
+ | `count` | number | Total number of selected items |
533
+
534
+ **Only valid inside `repeat` with `selection` config.** Throws error if used outside.
535
+
536
+ ### `$auth` — Authenticated user data
537
+ ```json
538
+ { "$auth": "email" }
539
+ { "$auth": "role" }
540
+ { "$auth": "isAuthenticated" }
541
+ { "$auth": "metadata.department" }
542
+ ```
543
+ Reads from the authenticated user's data. **Security whitelist** — only safe fields resolve. Sensitive fields (token, password, secret, session, and variants) always return `undefined`. See blocked fields list below.
544
+
545
+ | Field | Returns |
546
+ |-------|---------|
547
+ | `isAuthenticated` | boolean — false when no auth state |
548
+ | `id`, `email`, `name`, `avatar` | User profile fields |
549
+ | `role` | Primary role (string) |
550
+ | `roles` | All roles (string[]) |
551
+ | `metadata` | Full metadata object |
552
+ | `metadata.*` | Nested metadata field (dot notation) |
553
+ | `user` | Full user object |
554
+ | `loading` | Auth operation in progress |
555
+ | `error` | Last auth error message or null |
556
+
557
+ **Blocked fields:** `token`, `accessToken`, `access_token`, `refreshToken`, `refresh_token`, `password`, `secret`, `session`, `metadata.token`, `metadata.password` — always return `undefined`.
558
+
559
+ **Prefer `$auth` over `$state` for auth data** — `$auth` has security whitelist, clear semantics, and stable paths.
560
+
561
+ ---
562
+
563
+ ## Available Primitives
564
+
565
+ These are the built-in visual building blocks. Use them as the `type` field in elements.
566
+
567
+ **Layout:** `box`, `text`, `image`, `icon`, `stack`, `grid`, `scroll`, `divider`, `spacer`
568
+ **Form:** `input`, `textarea`, `select`, `checkbox`, `toggle`, `slider`
569
+ **Interaction:** `button`, `touchable`, `list`
570
+ **Overlays:** `modal`, `drawer`, `tabs`, `accordion`, `wizard`, `screen`
571
+ **Data visualization:** `bar-chart`, `line-chart`, `pie-chart`, `area-chart`, `table`, `kanban-board`
572
+ **Special:** `file-upload`, `camera`, `signature`, `audio-player`
573
+
574
+ Each primitive accepts `style` (CSS object), `visible` (condition), and `permission` (role-based access).
575
+
576
+ ### Icon Primitive
577
+
578
+ The `icon` primitive renders icons from a connected icon library. By default it's a placeholder — apps connect a library via `plugins.overridePrimitive('icon', ...)`.
579
+
580
+ ```json
581
+ { "type": "icon", "props": { "name": "pencil-simple", "size": 16, "weight": "bold", "color": "#666" } }
582
+ ```
583
+
584
+ | Prop | Type | Default | Purpose |
585
+ |------|------|---------|---------|
586
+ | `name` | string | `"circle"` | Icon name in kebab-case (e.g., `pencil-simple`, `trash`, `moon`) |
587
+ | `size` | number | `24` | Icon width/height in pixels |
588
+ | `weight` | string | `"regular"` | `thin`, `light`, `regular`, `bold`, `fill`, `duotone` |
589
+ | `color` | string | — | CSS color value. Supports `$token` expressions |
590
+
591
+ Icon names are library-agnostic (kebab-case). The connected library resolves them to the actual icon component.
592
+
593
+ **Icon inside a button:**
594
+ ```json
595
+ "edit-btn": {
596
+ "type": "button",
597
+ "props": { "style": { "display": "inline-flex", "alignItems": "center" } },
598
+ "children": ["edit-icon"],
599
+ "on": { "press": { "action": "openModal", "params": { "id": "edit-modal" } } }
600
+ },
601
+ "edit-icon": {
602
+ "type": "icon",
603
+ "props": { "name": "pencil-simple", "size": 16, "weight": "bold", "color": { "$token": "colors.textMuted" } }
604
+ }
605
+ ```
606
+
607
+ **Conditional icon name (e.g., theme toggle):**
608
+ ```json
609
+ "theme-icon": {
610
+ "type": "icon",
611
+ "props": {
612
+ "name": { "$cond": { "$state": "/preferences/theme", "eq": "dark" }, "$then": "sun", "$else": "moon" },
613
+ "size": 16, "weight": "bold"
614
+ }
615
+ }
616
+ ```
617
+
618
+ ### Modal & Drawer Auto-Visibility
619
+
620
+ Modals and drawers **automatically manage their own visibility**. You do NOT need to add a `visible` condition. The engine binds:
621
+ - `type: "modal"` → visible when `/ui/modals/{elementId}` is truthy
622
+ - `type: "drawer"` → visible when `/ui/drawers/{elementId}` is truthy
623
+
624
+ Use `openModal`/`closeModal` actions to control them:
625
+ ```json
626
+ "my-dialog": {
627
+ "type": "modal",
628
+ "props": { "title": "Confirm Action" },
629
+ "children": ["dialog-content"]
630
+ }
631
+ ```
632
+ Open it with: `{ "action": "openModal", "params": { "id": "my-dialog" } }`
633
+ Close it with: `{ "action": "closeModal", "params": { "id": "my-dialog" } }`
634
+
635
+ The `modal` primitive renders a `title` prop as an h2 heading above the children.
636
+
637
+ **Drawer example:**
638
+ ```json
639
+ "detail-drawer": {
640
+ "type": "drawer",
641
+ "props": { "side": "right", "width": 400, "style": { "padding": 24 } },
642
+ "children": ["drawer-content"]
643
+ }
644
+ ```
645
+ Open it with: `{ "action": "openDrawer", "params": { "id": "detail-drawer" } }`
646
+ Close it with: `{ "action": "closeDrawer", "params": { "id": "detail-drawer" } }`
647
+
648
+ | Prop | Type | Default | Purpose |
649
+ |------|------|---------|---------|
650
+ | `side` | string | `"left"` | `"left"` or `"right"` — which edge the drawer slides from |
651
+ | `width` | number/string | `300` | Width of the drawer panel |
652
+
653
+ ### Toast Notifications
654
+
655
+ Toasts render automatically — no configuration needed. The `showNotification` action writes to `/ui/notifications` and the renderer displays toasts with entry/exit animations, auto-dismiss, and type-based icons.
656
+
657
+ **Basic usage:**
658
+ ```json
659
+ { "action": "showNotification", "params": { "message": "Task created successfully", "type": "success" } }
660
+ ```
661
+
662
+ **With title:**
663
+ ```json
664
+ { "action": "showNotification", "params": { "title": "Error", "message": "Could not save. Server returned 500.", "type": "error" } }
665
+ ```
666
+
667
+ **Persistent (no auto-dismiss):**
668
+ ```json
669
+ { "action": "showNotification", "params": { "message": "Unsaved changes", "type": "warning", "duration": null } }
670
+ ```
671
+
672
+ **Custom duration:**
673
+ ```json
674
+ { "action": "showNotification", "params": { "message": "Copied!", "type": "info", "duration": 2000 } }
675
+ ```
676
+
677
+ **Dismiss programmatically:**
678
+ ```json
679
+ { "action": "dismissNotification", "params": { "id": "notification-id" } }
680
+ ```
681
+
682
+ | Param | Type | Default | Description |
683
+ |-------|------|---------|-------------|
684
+ | `message` | string | — | Toast message text (required) |
685
+ | `type` | string | `"info"` | `success`, `error`, `warning`, `info` |
686
+ | `title` | string | — | Optional bold heading above message |
687
+ | `duration` | number or null | container default (4000) | Auto-dismiss time in ms. `null` = persistent |
688
+
689
+ **Override with toast-container element:**
690
+
691
+ By default, toasts appear at top-right with 4s duration. For custom positioning or behavior, add a `toast-container` element to your spec:
692
+
693
+ ```json
694
+ "notifications": {
695
+ "type": "toast-container",
696
+ "props": {
697
+ "position": "bottom-center",
698
+ "duration": 6000,
699
+ "maxVisible": 3,
700
+ "offset": 16,
701
+ "gap": 12
702
+ }
703
+ }
704
+ ```
705
+
706
+ | Prop | Type | Default | Description |
707
+ |------|------|---------|-------------|
708
+ | `position` | string | `"top-right"` | `top-right`, `top-left`, `top-center`, `bottom-right`, `bottom-left`, `bottom-center` |
709
+ | `duration` | number | `4000` | Default auto-dismiss time in ms |
710
+ | `maxVisible` | number | `5` | Maximum toasts shown at once |
711
+ | `offset` | number | `24` | Distance from viewport edge in px |
712
+ | `gap` | number | `8` | Space between toasts in px |
713
+
714
+ Without a `toast-container` in the spec, defaults are used automatically. Icons use the connected icon library (Phosphor, Lucide, etc.) or fallback SVGs.
715
+
716
+ ### Device Context (Auto-Tracked)
717
+
718
+ The renderer automatically tracks device information and writes it to state. Specs can read these values for responsive behavior, platform detection, and OS preferences.
719
+
720
+ | State Path | Type | Description |
721
+ |-----------|------|-------------|
722
+ | `/ui/device/viewportWidth` | number | Current viewport width in px |
723
+ | `/ui/device/viewportHeight` | number | Current viewport height in px |
724
+ | `/ui/device/platform` | string | `"web"`, `"ios"`, or `"android"` |
725
+ | `/ui/device/orientation` | string | `"portrait"` or `"landscape"` (auto-computed) |
726
+ | `/ui/device/colorScheme` | string | OS preference: `"light"` or `"dark"` |
727
+
728
+ All values update in real-time (viewport on resize, colorScheme on OS change).
729
+
730
+ **Usage in specs:**
731
+ ```json
732
+ { "$breakpoint": { "sm": 1, "md": 2, "lg": 4 } }
733
+ { "$platform": { "web": "blur(24px)", "native": null } }
734
+ { "$platform": { "web": "layout-web", "native": "layout-native" } }
735
+ { "$cond": { "$state": "/ui/device/orientation", "eq": "portrait" }, "$then": "vertical", "$else": "horizontal" }
736
+ ```
737
+
738
+ **Opt-out:** If the host app manages device context manually, disable auto-tracking:
739
+ ```tsx
740
+ <MythikRenderer spec={spec} autoDeviceContext={false} />
741
+ ```
742
+
743
+ **Legacy compatibility:** `$breakpoint` reads `/ui/device/viewportWidth` first, falls back to `/ui/viewportWidth` for existing apps that write there manually.
744
+
745
+ ### Render Error Visibility
746
+
747
+ `createMythik({ security: { exposeErrors } })` controls how much render error detail the UI exposes.
748
+
749
+ ```tsx
750
+ const svc = createMythik({
751
+ security: { exposeErrors: false },
752
+ });
753
+
754
+ <MythikRenderer spec={spec} instance={svc} />
755
+ ```
756
+
757
+ - Default is `true` (`secConfig.exposeErrors !== false`).
758
+ - `_error` render nodes still write diagnostics to `/ui/renderErrors` when error exposure is enabled.
759
+ - Primitive/component exceptions are caught by a renderer-level error boundary. In development with `exposeErrors !== false`, the boundary renders a visible overlay with the error message and component stack.
760
+ - In production or with `exposeErrors: false`, the boundary renders a neutral placeholder and does not leak error details.
761
+ - If a broken spec is replaced with a valid spec, the boundary resets on spec change and renders the recovered content.
762
+
763
+ ### Table Primitive (Enhanced)
764
+
765
+ Feature-rich table with sorting, pagination, grouping, selection, and formatting. The AI enables/disables features with flags — no need to compose from primitives.
766
+
767
+ **Basic usage (backwards compatible):**
768
+ ```json
769
+ "my-table": {
770
+ "type": "table",
771
+ "props": {
772
+ "columns": [
773
+ { "key": "name", "label": "Name", "width": "2fr", "align": "left" },
774
+ { "key": "amount", "label": "Amount", "width": "1fr", "align": "right", "format": "currency", "formatOptions": { "currency": "HNL", "locale": "es-HN" } },
775
+ { "key": "status", "label": "Status", "width": "120px" }
776
+ ],
777
+ "data": { "$state": "/items" }
778
+ }
779
+ }
780
+ ```
781
+
782
+ Columns accept both `"key"` and `"field"` for the data field name.
783
+
784
+ **Full configuration:**
785
+ ```json
786
+ {
787
+ "type": "table",
788
+ "props": {
789
+ "data": { "$state": "/response/data" },
790
+ "columns": [ "..." ],
791
+ "sorting": { "enabled": true, "default": { "field": "amount", "direction": "desc" }, "mode": "client" },
792
+ "pagination": { "enabled": true, "pageSize": 20, "mode": "client" },
793
+ "selection": { "enabled": true, "mode": "multiple", "state": "/selectedIds" },
794
+ "groupBy": { "field": "institution", "header": true, "footer": "subtotal", "collapsible": true },
795
+ "stickyHeader": true,
796
+ "emptyState": { "icon": "inbox", "message": "No data" },
797
+ "rowStyle": { "hover": { "backgroundColor": "#F1F5F9" } },
798
+ "onRowClick": { "action": "openDrawer", "params": { "id": "detail-drawer" } }
799
+ }
800
+ }
801
+ ```
802
+
803
+ **Column properties:**
804
+
805
+ | Property | Type | Default | Description |
806
+ |----------|------|---------|-------------|
807
+ | `id` / `key` / `field` | string | — | Field name in data objects |
808
+ | `label` | string | — | Header text |
809
+ | `width` | string | `"1fr"` | CSS Grid width (`fr`, `px`, `%`) |
810
+ | `align` | `"left"` \| `"center"` \| `"right"` | `"left"` | Text alignment |
811
+ | `format` | `"currency"` \| `"number"` \| `"percent"` \| `"date"` | — | Auto-format cell values |
812
+ | `formatOptions` | object | — | `{ currency, locale, decimals }` |
813
+ | `sortable` | boolean | `true` | Column can be sorted |
814
+ | `visible` | boolean | `true` | Column visibility |
815
+ | `actions` | array | — | Action buttons in column: `[{ icon, color, onPress }]` |
816
+
817
+ **Sorting:** `{ enabled, mode, default, state }` — `"client"` sorts internally, `"server"` writes `{ field, direction }` to state (triggers dataSource re-fetch).
818
+
819
+ **Pagination:** `{ enabled, pageSize, mode, state, totalItems }` — `"client"` slices internally, `"server"` writes page number to state.
820
+
821
+ **Selection:** `{ enabled, mode, state }` — `"multiple"` adds checkbox column with select-all. `"single"` adds radio column.
822
+
823
+ **GroupBy:** `{ field, header, footer, expanded, collapsible }` — `footer: "subtotal"` auto-sums numeric columns.
824
+
825
+ ### Design Token Auto-Inheritance
826
+
827
+ All primitives automatically inherit from deep design tokens — not just colors but also shape (border-radius), typography (font family, size, weight), spacing (padding, gaps), elevation (shadows), motion (transitions), and opacity (disabled/pressed states). No `$token` references needed for primitive defaults. Use explicit `style` props only for per-element overrides. Sub-elements (modal title, input label, error text) also read from tokens.
828
+
829
+ ### Input Format Prop
830
+
831
+ The `input` primitive supports a `format` prop for display masking:
832
+ - `"phone"` — displays as (555) 123-4567, stores raw digits
833
+ - `"currency"` — displays as $1,234.56, stores raw number
834
+
835
+ ### Input Type: Color
836
+
837
+ `type: "color"` renders a native color picker. Use with `$bindState` for interactive color selection:
838
+ ```json
839
+ { "type": "input", "props": { "type": "color", "label": "Pick Color", "value": { "$bindState": "/form/color" } } }
840
+ ```
841
+
842
+ ---
843
+
844
+ ## Actions
845
+
846
+ Wire actions to events with the `on` property:
847
+
848
+ ```json
849
+ {
850
+ "on": {
851
+ "press": { "action": "navigate", "params": { "screen": "detail" } }
852
+ }
853
+ }
854
+ ```
855
+
856
+ ### Multiple Actions (Sequential)
857
+
858
+ Actions in an array execute sequentially — each async action (like `fetch`) completes before the next one starts:
859
+ ```json
860
+ {
861
+ "on": {
862
+ "press": [
863
+ { "action": "fetch", "params": { "url": "...", "method": "POST", "body": { ... } } },
864
+ { "action": "closeModal", "params": { "id": "my-modal" } },
865
+ { "action": "fetch", "params": { "url": "...", "method": "GET", "target": "/items" } }
866
+ ]
867
+ }
868
+ }
869
+ ```
870
+
871
+ ### fireAndForget Mode
872
+
873
+ Add `"fireAndForget": true` to dispatch an action without waiting for it to complete:
874
+ ```json
875
+ {
876
+ "action": "fetch",
877
+ "fireAndForget": true,
878
+ "params": { "url": "...", "method": "GET", "target": "/items" }
879
+ }
880
+ ```
881
+ The action executes in the background. The next action in the chain starts immediately.
882
+
883
+ **Use case:** Background re-fetches after closing a modal. The user sees the modal close instantly while the data refreshes behind the scenes.
884
+
885
+ ### Action Reference
886
+
887
+ | Action | Params | Purpose |
888
+ |--------|--------|---------|
889
+ | `setState` | `{ statePath, value }` | Set a value in state. Value can contain expressions and nested objects |
890
+ | `navigateScreen` | `{ screen, ...params }` | Navigate to a screen (MythikApp context) |
891
+ | `goBackScreen` | — | Go to previous screen using navigation history (MythikApp context) |
892
+ | `navigate` | `{ screen, ...params }` | Set navigation intent in state (low-level, prefer `navigateScreen`) |
893
+ | `goBack` | — | Set goBack intent in state (low-level, prefer `goBackScreen`) |
894
+ | `openModal` | `{ id }` | Show a modal (sets `/ui/modals/{id}` to true) |
895
+ | `closeModal` | `{ id }` | Hide a modal (sets `/ui/modals/{id}` to false) |
896
+ | `openDrawer` | `{ id }` | Open a drawer |
897
+ | `closeDrawer` | `{ id }` | Close a drawer |
898
+ | `showNotification` | `{ message, type }` | Show a toast/snackbar |
899
+ | `toggleTheme` | — | Switch dark/light mode |
900
+ | `setLocale` | `{ locale }` | Change language |
901
+ | `copyToClipboard` | `{ value }` | Copy to clipboard. Objects auto-stringified as JSON |
902
+ | `openUrl` | `{ url }` | Open external URL |
903
+ | `fetch` | `{ url, method, body, target, headers }` | HTTP request, writes response to target state path |
904
+ | `submitForm` | `{ url, method, body, target, successMessage }` | Validate + HTTP request + success notification |
905
+ | `dismissNotification` | `{ id }` | Remove a toast notification by id |
906
+ | `login` | `{ email, password, ... }` | Authenticate via auth provider. Writes user to `/auth/*` state. Clears credentials after. |
907
+ | `logout` | — | Clear session, tokens, state. Navigate to loginScreen. |
908
+ | `refreshSession` | — | Manual token refresh. Normally automatic (proactive refresh). |
909
+ | `validateForm` | `{ formId }` | Mark all fields touched, validate all, write errors |
910
+ | `touchField` | `{ formId, field }` | Mark single field touched and validate it |
911
+ | `resetForm` | `{ formId }` | Reset form to initial values, clear errors |
912
+ | `uploadFile` | `{ bucket, target, path? }` | Upload file(s) to storage via StorageAdapter. Writes URL(s) to target path |
913
+ | `deleteFile` | `{ path, bucket }` | Delete file from storage |
914
+ | `export` | `{ source, columns, filename, format? }` | Export data as CSV (built-in) or via adapter (XLSX/PDF). Triggers download |
915
+ | `updateTokens` | `{ dna?: {...}, identity?: {...}, _replace?: true }` | Hot-swap design tokens at runtime. Deep-merges by default; `_replace: true` fully replaces. Persists raw config to `/tokens/raw`. Numeric DNA seeds > 1 auto-normalized (÷100) |
916
+
917
+ ### fetch Action Details
918
+
919
+ The `fetch` action supports:
920
+ - **Custom headers:** `"headers": { "apikey": "...", "Authorization": "Bearer ..." }`
921
+ - **Body with expressions:** Nested expressions in `body` are deeply resolved before sending
922
+ - **Empty string sanitization:** Empty strings in body are automatically converted to `null` (prevents database errors for typed columns)
923
+ - **Loading state:** Sets `/ui/loading` to true while in flight, false when done
924
+ - **Error handling:** On failure, sets `/ui/lastError` with status and message
925
+
926
+ ```json
927
+ {
928
+ "action": "fetch",
929
+ "params": {
930
+ "url": { "$template": "${/config/apiUrl}/rest/v1/tasks" },
931
+ "method": "POST",
932
+ "headers": { "$state": "/config/headers" },
933
+ "body": {
934
+ "title": { "$state": "/form/title" },
935
+ "description": { "$state": "/form/description" },
936
+ "due_date": { "$state": "/form/dueDate" }
937
+ },
938
+ "target": "/lastResult"
939
+ }
940
+ }
941
+ ```
942
+
943
+ ### Transactions (Optimistic Updates)
944
+
945
+ Use `transaction` for CRUD operations that need instant UI feedback. The framework takes a state snapshot, applies your changes instantly, then confirms with the server. On failure, it rolls back automatically.
946
+
947
+ ```json
948
+ {
949
+ "on": {
950
+ "press": {
951
+ "transaction": {
952
+ "before": [/* UI transitions — NOT rolled back */],
953
+ "optimistic": [/* Data changes — ROLLED BACK on error */],
954
+ "confirm": [/* Network request — determines success/failure */],
955
+ "onSuccess": [/* Post-success sync (optional) */],
956
+ "onError": [/* User notification (optional, runs after rollback) */]
957
+ }
958
+ }
959
+ }
960
+ }
961
+ ```
962
+
963
+ **Decision rule:**
964
+ - **"Would I undo this if the server says no?"** → `optimistic`
965
+ - **"Should this happen regardless?"** → `before`
966
+
967
+ | Action | Phase | Why |
968
+ |--------|-------|-----|
969
+ | `closeModal` | `before` | Don't reopen on error |
970
+ | `setState` (data) | `optimistic` | Rollback if server rejects |
971
+ | `setState` (form reset) | `optimistic` | Restore form so user can retry |
972
+ | `navigate` | `before` | Stay on page, show error there |
973
+
974
+ **Confirm result access:** The `fetch` in `confirm` can write to `/tx/result` via `target`. `onSuccess` reads it with `{ "$state": "/tx/result" }`. On error, `/tx/error` contains `{ message }`. Both are cleaned up automatically.
975
+
976
+ **Timeout:** Default 10 seconds. Override with `timeout` (ms):
977
+ ```json
978
+ { "transaction": { "timeout": 30000, "confirm": [...] } }
979
+ ```
980
+
981
+ ### Transaction Templates
982
+
983
+ Copy the template for your CRUD operation, replace the `{{PLACEHOLDERS}}`.
984
+
985
+ #### CREATE — Add item to list
986
+
987
+ ```json
988
+ {
989
+ "transaction": {
990
+ "before": [
991
+ { "action": "closeModal", "params": { "id": "{{MODAL_ID}}" } }
992
+ ],
993
+ "optimistic": [
994
+ { "action": "setState", "params": {
995
+ "statePath": "{{ARRAY_PATH}}",
996
+ "value": { "$array": "append", "source": { "$state": "{{ARRAY_PATH}}" }, "value": {
997
+ "id": { "$template": "temp-${/form/title}" },
998
+ "{{FIELD_1}}": { "$state": "/form/{{FIELD_1}}" },
999
+ "{{FIELD_2}}": { "$state": "/form/{{FIELD_2}}" }
1000
+ }}
1001
+ }}
1002
+ ],
1003
+ "confirm": [
1004
+ { "action": "fetch", "params": {
1005
+ "method": "POST",
1006
+ "url": "{{API_URL}}",
1007
+ "headers": { "Prefer": "return=representation" },
1008
+ "body": {
1009
+ "{{FIELD_1}}": { "$state": "/form/{{FIELD_1}}" },
1010
+ "{{FIELD_2}}": { "$state": "/form/{{FIELD_2}}" }
1011
+ },
1012
+ "target": "/tx/result"
1013
+ }}
1014
+ ],
1015
+ "onSuccess": [
1016
+ { "action": "fetch", "params": {
1017
+ "method": "GET",
1018
+ "url": "{{API_URL}}?select=*&order=created_at.desc",
1019
+ "target": "{{ARRAY_PATH}}"
1020
+ }}
1021
+ ],
1022
+ "onError": [
1023
+ { "action": "showNotification", "params": { "type": "error", "message": "Could not create {{ENTITY}}. Please try again." } }
1024
+ ]
1025
+ }
1026
+ }
1027
+ ```
1028
+
1029
+ #### UPDATE — Edit item in list
1030
+
1031
+ ```json
1032
+ {
1033
+ "transaction": {
1034
+ "before": [
1035
+ { "action": "closeModal", "params": { "id": "{{MODAL_ID}}" } }
1036
+ ],
1037
+ "optimistic": [
1038
+ { "action": "setState", "params": {
1039
+ "statePath": "{{ARRAY_PATH}}",
1040
+ "value": { "$array": "replace", "source": { "$state": "{{ARRAY_PATH}}" },
1041
+ "where": { "field": "id", "eq": { "$state": "/form/editId" } },
1042
+ "value": {
1043
+ "id": { "$state": "/form/editId" },
1044
+ "{{FIELD_1}}": { "$state": "/form/{{FIELD_1}}" },
1045
+ "{{FIELD_2}}": { "$state": "/form/{{FIELD_2}}" }
1046
+ }
1047
+ }
1048
+ }}
1049
+ ],
1050
+ "confirm": [
1051
+ { "action": "fetch", "params": {
1052
+ "method": "PATCH",
1053
+ "url": { "$template": "{{API_URL}}?id=eq.${/form/editId}" },
1054
+ "headers": { "Prefer": "return=representation" },
1055
+ "body": {
1056
+ "{{FIELD_1}}": { "$state": "/form/{{FIELD_1}}" },
1057
+ "{{FIELD_2}}": { "$state": "/form/{{FIELD_2}}" }
1058
+ },
1059
+ "target": "/tx/result"
1060
+ }}
1061
+ ],
1062
+ "onError": [
1063
+ { "action": "showNotification", "params": { "type": "error", "message": "Could not update {{ENTITY}}. Please try again." } }
1064
+ ]
1065
+ }
1066
+ }
1067
+ ```
1068
+
1069
+ Note: UPDATE has no `onSuccess` — the optimistic data is already correct. A re-fetch would cause a visible flash.
1070
+
1071
+ #### DELETE — Remove item from list
1072
+
1073
+ ```json
1074
+ {
1075
+ "transaction": {
1076
+ "before": [
1077
+ { "action": "closeModal", "params": { "id": "{{CONFIRM_MODAL_ID}}" } }
1078
+ ],
1079
+ "optimistic": [
1080
+ { "action": "setState", "params": {
1081
+ "statePath": "{{ARRAY_PATH}}",
1082
+ "value": { "$array": "remove", "source": { "$state": "{{ARRAY_PATH}}" },
1083
+ "where": { "field": "id", "eq": { "$state": "/ui/deleteId" } }
1084
+ }
1085
+ }}
1086
+ ],
1087
+ "confirm": [
1088
+ { "action": "fetch", "params": {
1089
+ "method": "DELETE",
1090
+ "url": { "$template": "{{API_URL}}?id=eq.${/ui/deleteId}" }
1091
+ }}
1092
+ ],
1093
+ "onError": [
1094
+ { "action": "showNotification", "params": { "type": "error", "message": "Could not delete {{ENTITY}}. Please try again." } }
1095
+ ]
1096
+ }
1097
+ }
1098
+ ```
1099
+
1100
+ #### TOGGLE — Flip a boolean value
1101
+
1102
+ ```json
1103
+ {
1104
+ "transaction": {
1105
+ "optimistic": [
1106
+ { "action": "setState", "params": {
1107
+ "statePath": "{{BOOLEAN_PATH}}",
1108
+ "value": { "$not": { "$state": "{{BOOLEAN_PATH}}" } }
1109
+ }}
1110
+ ],
1111
+ "confirm": [
1112
+ { "action": "fetch", "params": {
1113
+ "method": "PATCH",
1114
+ "url": { "$template": "{{API_URL}}?id=eq.{{ITEM_ID}}" },
1115
+ "body": { "{{FIELD}}": { "$state": "{{BOOLEAN_PATH}}" } }
1116
+ }}
1117
+ ],
1118
+ "onError": [
1119
+ { "action": "showNotification", "params": { "type": "error", "message": "Could not update. Please try again." } }
1120
+ ]
1121
+ }
1122
+ }
1123
+ ```
1124
+
1125
+ Note: TOGGLE has no `before` — there's no modal to close.
1126
+
1127
+ ### Legacy CRUD Pattern (without transactions)
1128
+
1129
+ For simple cases that don't need optimistic updates, use sequential action arrays:
1130
+
1131
+ ```json
1132
+ {
1133
+ "on": {
1134
+ "press": [
1135
+ { "action": "closeModal", "params": { "id": "edit-modal" } },
1136
+ { "action": "fetch", "params": { "url": "...", "method": "PATCH", "body": { ... } } },
1137
+ { "action": "fetch", "params": { "url": "...", "method": "GET", "target": "/items" } }
1138
+ ]
1139
+ }
1140
+ }
1141
+ ```
1142
+
1143
+ The modal closes instantly. The PATCH waits for completion, then GET fetches fresh data. The user sees the list update ~500ms later. **Prefer transactions for instant UX.**
1144
+
1145
+ ---
1146
+
1147
+ ## Validation
1148
+
1149
+ Add validation to input fields:
1150
+
1151
+ ```json
1152
+ {
1153
+ "type": "input",
1154
+ "props": {
1155
+ "value": { "$bindState": "/form/email" },
1156
+ "checks": [
1157
+ { "type": "required", "message": "Email is required" },
1158
+ { "type": "email", "message": "Invalid email format" }
1159
+ ],
1160
+ "validateOn": "blur"
1161
+ }
1162
+ }
1163
+ ```
1164
+
1165
+ | Validator | Args | Purpose |
1166
+ |-----------|------|---------|
1167
+ | `required` | — | Non-empty value |
1168
+ | `email` | — | Valid email format |
1169
+ | `minLength` | `{ min }` | Minimum string length |
1170
+ | `maxLength` | `{ max }` | Maximum string length |
1171
+ | `pattern` | `{ pattern }` | Regex match |
1172
+ | `min` | `{ min }` | Minimum number |
1173
+ | `max` | `{ max }` | Maximum number |
1174
+ | `numeric` | — | Must be a number |
1175
+ | `url` | — | Valid URL |
1176
+ | `matches` | `{ other }` | Must equal another field |
1177
+ | `equalTo` | `{ other }` | Must equal another field |
1178
+ | `greaterThan` | `{ other }` | Must be greater than another field |
1179
+ | `lessThan` | `{ other }` | Must be less than another field |
1180
+ | `requiredIf` | `{ field }` | Required when condition field is truthy |
1181
+
1182
+ ---
1183
+
1184
+ ## Repeat (Lists)
1185
+
1186
+ Render an element once per item in an array:
1187
+
1188
+ ```json
1189
+ "task-list": {
1190
+ "type": "stack",
1191
+ "repeat": { "statePath": "/tasks", "key": "id" },
1192
+ "children": ["task-row"]
1193
+ }
1194
+ ```
1195
+
1196
+ Use `source` for filtered/paginated lists:
1197
+ ```json
1198
+ "repeat": {
1199
+ "source": {
1200
+ "$array": "slice",
1201
+ "source": {
1202
+ "$array": "filter",
1203
+ "source": { "$state": "/tasks" },
1204
+ "where": { "field": "status", "eq": { "$state": "/filter/status" } }
1205
+ },
1206
+ "from": { "$math": "multiply", "args": [{ "$state": "/page" }, 10] },
1207
+ "to": { "$math": "multiply", "args": [{ "$math": "add", "args": [{ "$state": "/page" }, 1] }, 10] }
1208
+ },
1209
+ "key": "id"
1210
+ }
1211
+ ```
1212
+
1213
+ Inside repeated elements, use `$item` and `$index` to access the current item's data. These work in `props`, `style`, `visible`, and `on` (event params).
1214
+
1215
+ ### Grouped Repeat (`groupBy`)
1216
+
1217
+ Two modes — auto-detected:
1218
+
1219
+ **Mode 1: Client-Side Grouping** — engine groups a flat array by field value:
1220
+
1221
+ ```json
1222
+ "task-list": {
1223
+ "type": "stack",
1224
+ "repeat": {
1225
+ "source": { "$state": "/tasks" },
1226
+ "key": "id",
1227
+ "groupBy": "category",
1228
+ "groupHeader": ["category-header"],
1229
+ "groupFooter": ["category-subtotal"]
1230
+ },
1231
+ "children": ["task-row"]
1232
+ }
1233
+ ```
1234
+
1235
+ **Mode 2: Pre-Grouped Data** — server already grouped the data:
1236
+
1237
+ ```json
1238
+ "budget-table": {
1239
+ "type": "stack",
1240
+ "repeat": {
1241
+ "source": { "$state": "/response/groups" },
1242
+ "key": "name",
1243
+ "groupKey": "name",
1244
+ "groupItems": "rows",
1245
+ "groupHeader": ["institution-header"],
1246
+ "groupFooter": ["subtotal-row"],
1247
+ "footer": ["grand-total-row"]
1248
+ },
1249
+ "children": ["data-row"]
1250
+ }
1251
+ ```
1252
+
1253
+ Pre-grouped data format: `[{ "name": "Group A", "rows": [...], "subtotal": { "total": 50000 } }, ...]`
1254
+
1255
+ **Detection:** `groupBy` (string) → client-side. `groupKey` + `groupItems` → pre-grouped.
1256
+
1257
+ | Property | Type | Description |
1258
+ |----------|------|-------------|
1259
+ | `groupBy` | string | Field name for client-side grouping |
1260
+ | `groupKey` | string | Field in group objects containing the key (pre-grouped) |
1261
+ | `groupItems` | string | Field in group objects containing the items array (pre-grouped) |
1262
+ | `groupHeader` | string[] | Element IDs to render as group header (receives `$group` context) |
1263
+ | `groupFooter` | string[] | Element IDs to render as group footer (receives `$group` context) |
1264
+ | `footer` | string[] | Element IDs to render once after all groups |
1265
+
1266
+ Inside `groupHeader` and `groupFooter`, use `$group` expressions. Inside `children`, use `$item`/`$index` as usual.
1267
+
1268
+ ### Repeat Selection (`selection`)
1269
+
1270
+ Adds multi-select behavior to a repeat:
1271
+
1272
+ ```json
1273
+ "task-list": {
1274
+ "type": "stack",
1275
+ "repeat": {
1276
+ "source": { "$state": "/tasks" },
1277
+ "key": "id",
1278
+ "selection": {
1279
+ "state": "/selectedIds",
1280
+ "mode": "multiple"
1281
+ }
1282
+ },
1283
+ "children": ["task-row"]
1284
+ }
1285
+ ```
1286
+
1287
+ | Property | Type | Description |
1288
+ |----------|------|-------------|
1289
+ | `state` | string | State path for selected IDs array |
1290
+ | `key` | string | Field from each item used as the selection identifier |
1291
+ | `mode` | `"single"` \| `"multiple"` | Single selects one at a time, multiple allows many |
1292
+
1293
+ Inside the repeat, use `$selection` expressions:
1294
+ ```json
1295
+ "select-checkbox": {
1296
+ "type": "checkbox",
1297
+ "props": { "value": { "$selection": "selected" } },
1298
+ "on": { "change": { "action": "toggleSelection", "params": { "statePath": "/selectedIds", "value": { "$item": "id" } } } }
1299
+ }
1300
+ ```
1301
+
1302
+ **Selection helper actions:**
1303
+
1304
+ | Action | Params | Description |
1305
+ |--------|--------|-------------|
1306
+ | `toggleSelection` | `{ statePath, value, mode? }` | Add/remove value from selection array. `mode: "single"` replaces |
1307
+ | `selectAll` | `{ statePath, values }` | Set selection to provided values array |
1308
+ | `selectNone` | `{ statePath }` | Clear selection array |
1309
+
1310
+ ---
1311
+
1312
+ ## Visibility Conditions
1313
+
1314
+ ```json
1315
+ { "$state": "/user/role", "eq": "admin" }
1316
+ [{ "$state": "/form/isValid" }, { "$state": "/form/hasChanges" }]
1317
+ { "$or": [{ "$state": "/user/isVIP" }, { "$state": "/cart/total", "gt": 200 }] }
1318
+ { "$and": [{ "$state": "/user/active" }, { "$state": "/user/verified" }] }
1319
+ ```
1320
+
1321
+ ### Loading/Content/Empty Pattern
1322
+
1323
+ For data-driven screens, use this visibility pattern:
1324
+
1325
+ ```json
1326
+ "loading-section": {
1327
+ "visible": { "$and": [{ "$state": "/ui/loading" }, { "$not": { "$array": "count", "source": { "$state": "/items" } } }] }
1328
+ },
1329
+ "content-section": {
1330
+ "visible": { "$and": [{ "$not": { "$state": "/ui/lastError" } }, { "$array": "count", "source": { "$state": "/items" } }] }
1331
+ },
1332
+ "empty-section": {
1333
+ "visible": { "$and": [{ "$not": { "$state": "/ui/loading" } }, { "$not": { "$state": "/ui/lastError" } }, { "$not": { "$array": "count", "source": { "$state": "/items" } } }] }
1334
+ },
1335
+ "error-section": {
1336
+ "visible": { "$state": "/ui/lastError" }
1337
+ }
1338
+ ```
1339
+
1340
+ **Key:** Loading skeleton shows only on initial load (loading AND no data). Content stays visible during background re-fetches (data exists, regardless of loading state).
1341
+
1342
+ ---
1343
+
1344
+ ## Derived State (`derive`)
1345
+
1346
+ Auto-computed state values that update reactively when their dependencies change. Declared at the spec level.
1347
+
1348
+ ```json
1349
+ {
1350
+ "root": "...",
1351
+ "elements": { "..." },
1352
+ "derive": {
1353
+ "/stats/totalItems": {
1354
+ "$array": "count", "source": { "$state": "/items" }
1355
+ },
1356
+ "/stats/avgPrice": {
1357
+ "$math": "divide", "args": [
1358
+ { "$array": "sum", "source": { "$state": "/items" }, "field": "price" },
1359
+ { "$array": "count", "source": { "$state": "/items" } }
1360
+ ]
1361
+ },
1362
+ "/stats/completionRate": {
1363
+ "$math": "multiply", "args": [
1364
+ { "$math": "divide", "args": [
1365
+ { "$array": "count", "source": { "$state": "/tasks" }, "where": { "field": "status", "eq": "completed" } },
1366
+ { "$array": "count", "source": { "$state": "/tasks" } }
1367
+ ]},
1368
+ 100
1369
+ ]
1370
+ }
1371
+ }
1372
+ }
1373
+ ```
1374
+
1375
+ **How it works:**
1376
+ 1. On spec mount: all derive expressions are evaluated in dependency order (topological sort)
1377
+ 2. On state change: only derives whose dependencies changed re-evaluate
1378
+ 3. Derive paths are **read-only** — `setState` to a derive path is blocked by StateGuard
1379
+ 4. Circular dependencies are detected at mount time (throws error, not silent loop)
1380
+
1381
+ **Derive A can reference Derive B** — as long as there's no cycle. The engine evaluates them in topological order.
1382
+
1383
+ ---
1384
+
1385
+ ## Form Validation (`forms`)
1386
+
1387
+ Declarative form-level validation. Coordinates field validation as a unit, tracks form-level validity, supports cross-field rules, and gates `submitForm`.
1388
+
1389
+ ```json
1390
+ {
1391
+ "forms": {
1392
+ "task-form": {
1393
+ "fields": {
1394
+ "title": {
1395
+ "statePath": "/form/title",
1396
+ "rules": [{ "type": "required", "message": "Title is required" }]
1397
+ },
1398
+ "email": {
1399
+ "statePath": "/form/email",
1400
+ "rules": [{ "type": "required" }, { "type": "email", "message": "Invalid email" }]
1401
+ },
1402
+ "maxPrice": {
1403
+ "statePath": "/form/maxPrice",
1404
+ "rules": [
1405
+ { "type": "greaterThan", "args": { "other": { "$state": "/form/minPrice" } }, "message": "Max must exceed min" }
1406
+ ]
1407
+ }
1408
+ },
1409
+ "validateOn": "blur"
1410
+ }
1411
+ }
1412
+ }
1413
+ ```
1414
+
1415
+ ### Form State (auto-generated)
1416
+
1417
+ | Path | Type | Description |
1418
+ |------|------|-------------|
1419
+ | `/ui/forms/{formId}/isValid` | boolean | True when ALL fields pass validation |
1420
+ | `/ui/forms/{formId}/errorCount` | number | Total displayed errors across all fields |
1421
+ | `/ui/forms/{formId}/isDirty` | boolean | True when any field value differs from initial |
1422
+ | `/ui/forms/{formId}/fields/{fieldId}/errors` | string[] | Error messages (empty when valid) |
1423
+ | `/ui/forms/{formId}/fields/{fieldId}/touched` | boolean | User has interacted with this field |
1424
+ | `/ui/forms/{formId}/fields/{fieldId}/dirty` | boolean | Value differs from initial |
1425
+
1426
+ **Key behavior:** `isValid` reflects actual validity even for untouched fields (for submit button state). But `errors` are only shown for touched fields (no error messages on fields the user hasn't interacted with yet).
1427
+
1428
+ ### Form Actions
1429
+
1430
+ | Action | Params | Description |
1431
+ |--------|--------|-------------|
1432
+ | `validateForm` | `{ formId }` | Mark all fields as touched, validate all, write errors |
1433
+ | `touchField` | `{ formId, field }` | Mark one field as touched and validate it |
1434
+ | `resetForm` | `{ formId }` | Restore initial values, clear all errors/touched/dirty |
1435
+ | `submitForm` | `{ formId, url, ... }` | When `formId` present: validates first, blocks if invalid |
1436
+
1437
+ ### Spec Author Usage
1438
+
1439
+ ```json
1440
+ // Disable submit until form is valid
1441
+ "disabled": { "$not": { "$state": "/ui/forms/task-form/isValid" } }
1442
+
1443
+ // Show inline field error
1444
+ "error-text": {
1445
+ "type": "text",
1446
+ "visible": { "$array": "count", "source": { "$state": "/ui/forms/task-form/fields/title/errors" } },
1447
+ "props": {
1448
+ "content": { "$array": "first", "source": { "$state": "/ui/forms/task-form/fields/title/errors" } },
1449
+ "style": { "color": "#EF4444", "fontSize": 12 }
1450
+ }
1451
+ }
1452
+
1453
+ // Submit with validation gate
1454
+ { "action": "submitForm", "params": { "formId": "task-form", "url": "...", "method": "POST", "body": {...} } }
1455
+ ```
1456
+
1457
+ ### Compatibility
1458
+
1459
+ Forms are 100% opt-in. Existing specs with inline `checks` on inputs continue working. `submitForm` without `formId` behaves exactly as before.
1460
+
1461
+ ---
1462
+
1463
+ ## Data Sources (`dataSources`)
1464
+
1465
+ Declarative reactive data fetching. Makes specs self-contained — no host-app code needed for GET requests.
1466
+
1467
+ ```json
1468
+ {
1469
+ "root": "...",
1470
+ "elements": { "..." },
1471
+ "dataSources": {
1472
+ "tasks": {
1473
+ "url": { "$template": "${/config/apiUrl}/rest/v1/tasks?select=*&order=created_at.desc" },
1474
+ "method": "GET",
1475
+ "headers": { "$state": "/config/headers" },
1476
+ "params": {
1477
+ "status": { "$state": "/filter/status" },
1478
+ "page": { "$state": "/pagination/page" },
1479
+ "search": { "$state": "/filter/search" }
1480
+ },
1481
+ "target": "/tasks",
1482
+ "trigger": "auto",
1483
+ "debounce": 300,
1484
+ "initialFetch": true,
1485
+ "emptyWhileLoading": false
1486
+ }
1487
+ }
1488
+ }
1489
+ ```
1490
+
1491
+ | Property | Type | Default | Description |
1492
+ |----------|------|---------|-------------|
1493
+ | `url` | Expression | — | Request URL (supports `$template`, `$state`) |
1494
+ | `method` | string | `"GET"` | HTTP method |
1495
+ | `headers` | Expression | — | Request headers (from state, NOT from spec — keys must not live in the database) |
1496
+ | `params` | `Record<string, Expression>` | — | Reactive parameters. Changes trigger re-fetch |
1497
+ | `target` | string | — | State path where response is written |
1498
+ | `trigger` | `"auto"` \| `"manual"` | `"auto"` | `auto`: re-fetch when params change. `manual`: only via `refreshDataSource` action |
1499
+ | `debounce` | number | `0` | Debounce time in ms for auto-trigger |
1500
+ | `initialFetch` | boolean | `true` | Fetch on spec mount |
1501
+ | `emptyWhileLoading` | boolean | `false` | If `true`, clear target while loading. If `false`, keep previous data |
1502
+
1503
+ **Auto-generated state paths** for a dataSource with `"target": "/tasks"`:
1504
+ - `/tasks` — response data
1505
+ - `/tasksLoading` — boolean
1506
+ - `/tasksError` — error object or null
1507
+
1508
+ **Param filtering:** Values `null`, `undefined`, `""`, or `"all"` are omitted from the query string (inactive filter).
1509
+
1510
+ **Force re-fetch action:**
1511
+ ```json
1512
+ { "action": "refreshDataSource", "params": { "id": "tasks" } }
1513
+ ```
1514
+
1515
+ **Security:** Headers come from state, not from spec. API keys must NOT be in the spec (which lives in the database). The host app injects them via `initialState`:
1516
+ ```json
1517
+ "sharedState": {
1518
+ "config": { "headers": { "apikey": "...", "Authorization": "Bearer ..." } }
1519
+ }
1520
+ ```
1521
+
1522
+ **Interaction with `fetch` action:** `dataSources` handles reactive GET. `fetch` action handles one-shot operations (POST/PATCH/DELETE in transactions). They complement each other.
1523
+
1524
+ ---
1525
+
1526
+ ## Element Templates (`templates`)
1527
+
1528
+ Reusable element definitions within specs. Reduces repetition for patterns like monetary columns, stat cards, etc.
1529
+
1530
+ ```json
1531
+ {
1532
+ "templates": {
1533
+ "monetary-col": {
1534
+ "type": "text",
1535
+ "defaults": { "color": "#0F172A", "bold": 400, "currency": "HNL" },
1536
+ "props": {
1537
+ "content": {
1538
+ "$format": "currency",
1539
+ "value": { "$prop": "value" },
1540
+ "currency": { "$prop": "currency" },
1541
+ "locale": "es-HN"
1542
+ }
1543
+ },
1544
+ "style": {
1545
+ "textAlign": "right",
1546
+ "fontFamily": "monospace",
1547
+ "fontSize": 13,
1548
+ "fontWeight": { "$prop": "bold" },
1549
+ "color": { "$prop": "color" }
1550
+ }
1551
+ }
1552
+ },
1553
+ "elements": {
1554
+ "col-votado": {
1555
+ "type": "monetary-col",
1556
+ "props": { "value": { "$item": "allocatedAmount" } }
1557
+ },
1558
+ "col-mod": {
1559
+ "type": "monetary-col",
1560
+ "props": { "value": { "$item": "modificaciones" }, "color": "#D97706", "bold": 600 }
1561
+ }
1562
+ }
1563
+ }
1564
+ ```
1565
+
1566
+ **How it works:**
1567
+ 1. Engine encounters element with `type: "monetary-col"` (not a registered primitive)
1568
+ 2. Checks `spec.templates` → found
1569
+ 3. Merges element props with template `defaults` (element props win)
1570
+ 4. Expands template: replaces `$prop` references with merged props
1571
+ 5. Renders the resulting element as if defined inline
1572
+
1573
+ **Template properties:**
1574
+
1575
+ | Property | Type | Description |
1576
+ |----------|------|-------------|
1577
+ | `type` | string | Primitive type this template wraps |
1578
+ | `defaults` | `Record<string, unknown>` | Default prop values |
1579
+ | `props` | object | Props using `$prop` for parameterization |
1580
+ | `style` | object | Style using `$prop` |
1581
+ | `children` | `string[]` | Child element IDs. Use `"$children"` marker to insert the consuming element's children |
1582
+
1583
+ Templates support all Element fields: `visible`, `hover`, `active`, `motion`, etc.
1584
+
1585
+ **Templates with children:**
1586
+ ```json
1587
+ {
1588
+ "templates": {
1589
+ "card": {
1590
+ "type": "box",
1591
+ "defaults": { "padding": 16 },
1592
+ "style": { "padding": { "$prop": "padding" }, "borderRadius": 12, "backgroundColor": { "$token": "colors.surface" } },
1593
+ "children": ["$children"]
1594
+ }
1595
+ },
1596
+ "elements": {
1597
+ "my-card": { "type": "card", "children": ["title", "body"] }
1598
+ }
1599
+ }
1600
+ ```
1601
+
1602
+ `"$children"` is replaced with the consuming element's actual children at expansion time.
1603
+
1604
+ **Sharing:** Templates defined in an AppSpec are available to all screens. Screen-level templates override app-level templates.
1605
+
1606
+ ---
1607
+
1608
+ ## App Spec & Screen Outlet
1609
+
1610
+ `AppSpec` defines the app-level layout, navigation, shared state, and screen registry. Screens render inside a `screen-outlet` pseudo-primitive. **100% opt-in** — without AppSpec, everything works as today.
1611
+
1612
+ ```json
1613
+ {
1614
+ "type": "app",
1615
+ "name": "my-application",
1616
+ "navigation": {
1617
+ "type": "sidebar",
1618
+ "initialScreen": "task-manager",
1619
+ "breadcrumb": "history",
1620
+ "auth": {
1621
+ "loginScreen": "login",
1622
+ "protectedScreens": ["*"],
1623
+ "roleAccess": { "admin": ["*"], "user": ["task-manager", "profile"] }
1624
+ }
1625
+ },
1626
+ "screens": {
1627
+ "task-manager": {
1628
+ "label": "Task Manager",
1629
+ "icon": "clipboard-text",
1630
+ "roles": ["admin", "user"],
1631
+ "statePolicy": "preserve"
1632
+ },
1633
+ "new-task": {
1634
+ "label": "New Task",
1635
+ "icon": "plus",
1636
+ "roles": ["admin", "user"],
1637
+ "statePolicy": "reset",
1638
+ "parent": "task-manager"
1639
+ }
1640
+ },
1641
+ "tokens": { "colors": { "primary": "#0D9488" } },
1642
+ "translations": { "en": { "..." }, "es": { "..." } },
1643
+ "sharedState": { "preferences": { "theme": "light", "locale": "en" } },
1644
+ "templates": { "monetary-col": { "..." } },
1645
+ "layout": {
1646
+ "root": "app-shell",
1647
+ "elements": {
1648
+ "app-shell": { "type": "stack", "props": { "direction": "horizontal" }, "children": ["sidebar", "screen-outlet"] },
1649
+ "sidebar": { "type": "box", "children": ["nav-menu"] },
1650
+ "screen-outlet": { "type": "screen-outlet", "style": { "flex": 1 } }
1651
+ }
1652
+ }
1653
+ }
1654
+ ```
1655
+
1656
+ ### Screen Definition
1657
+
1658
+ | Property | Type | Default | Description |
1659
+ |----------|------|---------|-------------|
1660
+ | `label` | string or Expression | — | Display name (sidebar, breadcrumb) |
1661
+ | `icon` | string | — | Icon name (kebab-case) |
1662
+ | `roles` | `string[]` | `["*"]` | Which roles can access this screen (used only when `roleAccess` is NOT defined) |
1663
+ | `statePolicy` | `"preserve"` \| `"reset"` \| `"reload"` | `"preserve"` | State behavior on navigation |
1664
+ | `parent` | string | — | Parent screen ID (for hierarchy breadcrumb) |
1665
+
1666
+ ### State Policies
1667
+
1668
+ | Policy | On navigate away | On navigate back | Use case |
1669
+ |--------|-----------------|-----------------|----------|
1670
+ | `preserve` | State kept | Restores previous state | Lists, dashboards |
1671
+ | `reset` | State cleared | Fresh initialState | Create forms |
1672
+ | `reload` | State cleared | initialActions re-execute | Always-fresh data |
1673
+
1674
+ ### Inheritance
1675
+
1676
+ Screens inherit from AppSpec:
1677
+ - **Tokens**: deep merge (screen overrides app)
1678
+ - **Translations**: merged (screen extends app)
1679
+ - **Templates**: screen overrides app with same name
1680
+
1681
+ ### Auto-Generated State
1682
+
1683
+ | Path | Content |
1684
+ |------|---------|
1685
+ | `/navigation/currentScreen` | Active screen ID |
1686
+ | `/navigation/history` | Array of visited screen IDs |
1687
+ | `/navigation/breadcrumb` | Enriched array with label, icon |
1688
+ | `/app/screens` | Array of accessible screen definitions (filtered by role, excludes loginScreen) |
1689
+
1690
+ **Sidebar auto-filtering:** `/app/screens` only includes screens the current user can access. The loginScreen is always excluded from the sidebar. When the user logs in or roles change, call `refreshScreenList()` on the AppEngine to re-compute the sidebar.
1691
+
1692
+ ### Breadcrumb Modes
1693
+
1694
+ | Mode | Behavior |
1695
+ |------|----------|
1696
+ | `"history"` | Follows navigation stack |
1697
+ | `"hierarchy"` | Follows `parent` chain from current screen to root |
1698
+ | `"none"` | No auto-computed breadcrumb |
1699
+
1700
+ ### Access Control — roleAccess vs Screen Roles
1701
+
1702
+ Two models exist for controlling screen access. **Only one is active per app** — they don't mix.
1703
+
1704
+ **Model 1: `roleAccess` map (recommended for multi-role apps)**
1705
+
1706
+ Define in `navigation.auth.roleAccess`. This is the **source of truth** when present — `ScreenDefinition.roles` is ignored.
1707
+
1708
+ ```json
1709
+ "roleAccess": {
1710
+ "admin": ["*"],
1711
+ "user": ["task-manager", "dashboard", "profile"],
1712
+ "viewer": ["dashboard"]
1713
+ }
1714
+ ```
1715
+
1716
+ - A role listed with `["*"]` can access every screen.
1717
+ - A role NOT listed in `roleAccess` has **zero access** (denied everywhere).
1718
+ - The loginScreen is always accessible regardless of roleAccess — prevents infinite redirect loops.
1719
+
1720
+ **Model 2: `ScreenDefinition.roles` (backward compatible)**
1721
+
1722
+ When `roleAccess` is NOT defined (or undefined), each screen's `roles` array controls access:
1723
+
1724
+ ```json
1725
+ "screens": {
1726
+ "dashboard": { "label": "Dashboard", "roles": ["admin", "user", "viewer"] },
1727
+ "admin-panel": { "label": "Admin", "roles": ["admin"] }
1728
+ }
1729
+ ```
1730
+
1731
+ - `roles: ["*"]` (default) means any role can access the screen.
1732
+
1733
+ **Navigation behavior on access denial:**
1734
+
1735
+ | User state | Behavior |
1736
+ |------------|----------|
1737
+ | **Unauthenticated** (no role, no `/auth/isAuthenticated`) | Redirect to `loginScreen` |
1738
+ | **Authenticated but unauthorized** (`/auth/isAuthenticated` is true, role doesn't match) | Stay on current screen, set `/auth/error` with denial message — **no redirect to login** |
1739
+
1740
+ **Sidebar auto-refresh:**
1741
+
1742
+ `MythikApp` automatically re-computes `/app/screens` when auth state changes (`/auth/user/*` or `/auth/isAuthenticated`). No manual call needed — login, logout, session restore, cross-tab sync, and role changes all trigger sidebar refresh automatically. If building a custom host app without `MythikApp`, call `appEngine.refreshScreenList()` after auth state changes.
1743
+
1744
+ ---
1745
+
1746
+ ## Interactive States
1747
+
1748
+ Add visual feedback to elements with `hover`, `active`, `focus`, and `transition`. Zero overhead for elements without interactions.
1749
+
1750
+ **CSS vs Motion — automatic detection:** If hover/active/focus contains any of these 6 transform properties → Motion wrapper: `scale`, `scaleX`, `scaleY`, `rotate`, `x`, `y`. Everything else → CSS `:hover`/`:active`/`:focus-visible` pseudo-classes (no wrapper, no extra DOM node).
1751
+
1752
+ ### Hover — pointer enters
1753
+
1754
+ ```json
1755
+ "hover": { "scale": 1.03, "backgroundColor": { "$token": "colors.primaryHover" } }
1756
+ ```
1757
+
1758
+ ### Active — element is pressed
1759
+
1760
+ ```json
1761
+ "active": { "scale": 0.97 }
1762
+ ```
1763
+
1764
+ ### Focus — keyboard focus
1765
+
1766
+ ```json
1767
+ "focus": { "boxShadow": "0 0 0 3px rgba(13,148,136,0.3)" }
1768
+ ```
1769
+
1770
+ ### Transition — how changes animate
1771
+
1772
+ ```json
1773
+ "transition": { "duration": 200, "ease": "easeOut" }
1774
+ ```
1775
+
1776
+ ### Transform shorthand
1777
+
1778
+ Use `scale`, `x`, `y`, `rotate` directly — no CSS transform strings needed:
1779
+
1780
+ | Property | Effect |
1781
+ |----------|--------|
1782
+ | `scale` | Scale up/down |
1783
+ | `x` | Move horizontally |
1784
+ | `y` | Move vertically |
1785
+ | `rotate` | Rotate in degrees |
1786
+ | `scaleX`, `scaleY` | Axis-specific scale |
1787
+
1788
+ ### Complete example
1789
+
1790
+ ```json
1791
+ "save-btn": {
1792
+ "type": "button",
1793
+ "props": { "label": "Save Changes" },
1794
+ "style": { "backgroundColor": { "$token": "colors.primary" }, "color": "#fff", "padding": "10px 20px", "borderRadius": 10 },
1795
+ "hover": { "scale": 1.03, "y": -1 },
1796
+ "active": { "scale": 0.97 },
1797
+ "focus": { "boxShadow": "0 0 0 3px rgba(13,148,136,0.3)" },
1798
+ "transition": { "duration": 150, "ease": "easeOut" }
1799
+ }
1800
+ ```
1801
+
1802
+ All fields support `$token`, `$state`, `$cond`, and other expressions.
1803
+
1804
+ ---
1805
+
1806
+ ## Animations
1807
+
1808
+ ### Mount animation (fade in, slide up, etc.)
1809
+
1810
+ ```json
1811
+ "card": {
1812
+ "type": "box",
1813
+ "props": {},
1814
+ "motion": {
1815
+ "initial": { "opacity": 0, "y": 20 },
1816
+ "animate": { "opacity": 1, "y": 0 },
1817
+ "transition": { "duration": 0.3, "ease": "easeOut" }
1818
+ }
1819
+ }
1820
+ ```
1821
+
1822
+ ### Exit animation
1823
+
1824
+ ```json
1825
+ "motion": {
1826
+ "initial": { "opacity": 0 },
1827
+ "animate": { "opacity": 1 },
1828
+ "exit": { "opacity": 0, "y": -10 }
1829
+ }
1830
+ ```
1831
+
1832
+ ### Staggered children
1833
+
1834
+ Parent defines stagger, children animate in sequence:
1835
+
1836
+ ```json
1837
+ "list": {
1838
+ "type": "stack",
1839
+ "motion": {
1840
+ "animate": {},
1841
+ "transition": { "staggerChildren": 0.05 }
1842
+ },
1843
+ "children": ["card-1", "card-2", "card-3"]
1844
+ }
1845
+ ```
1846
+
1847
+ Each child should have its own `motion.initial`/`motion.animate`.
1848
+
1849
+ ### Motion design tokens
1850
+
1851
+ Define once at app level alongside `colors`, `spacing`, `radius`:
1852
+
1853
+ ```json
1854
+ {
1855
+ "tokens": {
1856
+ "motion": {
1857
+ "duration": { "fast": 150, "normal": 250, "slow": 400 },
1858
+ "ease": {
1859
+ "default": "easeOut",
1860
+ "spring": { "type": "spring", "stiffness": 300, "damping": 20 },
1861
+ "smooth": [0.16, 1, 0.3, 1]
1862
+ },
1863
+ "presets": {
1864
+ "hoverLift": { "y": -2, "scale": 1.02 },
1865
+ "hoverGlow": { "boxShadow": "0 0 0 3px rgba(13,148,136,0.2)" },
1866
+ "press": { "scale": 0.97 },
1867
+ "fadeIn": { "initial": { "opacity": 0 }, "animate": { "opacity": 1 } },
1868
+ "slideUp": { "initial": { "opacity": 0, "y": 20 }, "animate": { "opacity": 1, "y": 0 } },
1869
+ "scaleIn": { "initial": { "scale": 0.9, "opacity": 0 }, "animate": { "scale": 1, "opacity": 1 } }
1870
+ }
1871
+ }
1872
+ }
1873
+ }
1874
+ ```
1875
+
1876
+ Reference tokens or write custom values:
1877
+
1878
+ ```json
1879
+ "hover": { "$token": "motion.presets.hoverLift" }
1880
+ "transition": { "duration": { "$token": "motion.duration.fast" } }
1881
+ ```
1882
+
1883
+ ---
1884
+
1885
+ ## Rules
1886
+
1887
+ 1. **Always use the flat tree structure** — root + elements map with string IDs for children
1888
+ 2. **Use `$token` for all visual values** — never hardcode colors, spacing, fonts
1889
+ 3. **Use `$bindState` for form inputs** — enables two-way data binding
1890
+ 4. **Use `$cond` for dynamic styles** — conditional colors, sizes, visibility
1891
+ 5. **Use `$template` for computed strings** — interpolation over concatenation
1892
+ 6. **Use `$math`, `$array`, `$date`, `$format` instead of `$computed`** — built-in operations cover 90%+ of cases without code
1893
+ 7. **Use `$and`/`$or`/`$not` for boolean logic** — works in `disabled`, `style`, `visible`, anywhere
1894
+ 8. **Use `$array: 'search'` for filtering lists** — case-insensitive multi-field search
1895
+ 9. **Use `repeat.source` for paginated/filtered lists** — chain `$array: 'filter'` → `$array: 'search'` → `$array: 'slice'`
1896
+ 10. **Don't add `visible` to modals/drawers** — the engine manages it automatically via `/ui/modals/{id}`
1897
+ 11. **Use `$let` array format when stored in JSONB** — `[["a", expr], ["b", expr]]` preserves order
1898
+ 12. **Use `transaction` for all CRUD operations** — instant UX with automatic rollback on failure
1899
+ 13. **In transactions: `closeModal` goes in `before`, data changes in `optimistic`** — before is not rolled back, optimistic is
1900
+ 14. **UPDATE transactions don't need `onSuccess`** — optimistic data is already correct, re-fetch causes visible flash
1901
+ 15. **Use `type: 'icon'` with `name` prop for icons** — not emoji text. Names are kebab-case: `pencil-simple`, `trash`, `moon`
1902
+ 16. **`$array: 'append'` and `$array: 'replace'` use `value`, not `item`** — `item` silently does nothing
1903
+ 17. **Field IDs must be unique within the spec** — use descriptive names: `patient-name`, `submit-btn`
1904
+ 18. **Arrays for ordered collections** — never objects
1905
+ 19. **Use `hover`/`active`/`focus` for interactive feedback** — not inline styles or event handlers
1906
+ 20. **Use `transition` to control animation speed** — default duration is fine for most cases
1907
+ 21. **Use `motion.initial`/`motion.animate` for entrance animations** — elements animate when they first appear
1908
+ 22. **Buttons and touchables have built-in CSS transitions** — only add explicit `hover`/`active` when you need custom behavior beyond the defaults
1909
+ 23. **Use `$switch` for 3+ branches** — replaces deeply nested `$cond`. `default` is required
1910
+ 24. **Use `$format` with `locale` for localized numbers** — `"locale": "es-HN"` for country-specific formatting
1911
+ 25. **Use `derive` for computed stats** — auto-computed, dependency-tracked, read-only. Don't recompute in multiple elements
1912
+ 26. **Use `dataSources` for reactive GET** — self-contained specs. `fetch` action for one-shot POST/PATCH/DELETE
1913
+ 27. **Use `repeat.groupBy` for grouped data** — client-side (`groupBy` field) or pre-grouped (`groupKey` + `groupItems`)
1914
+ 28. **Use templates for repetitive elements** — 7 identical columns become 7 one-liners with `$prop`
1915
+ 29. **Use AppSpec for multi-screen apps** — sidebar, navigation, shared state, tokens, translations all in JSON
1916
+ 30. **Table `mode: "server"` for large datasets** — writes sort/page to state, `dataSources` re-fetches
1917
+ 31. **`$group` only inside groupBy context** — throws error if used in regular elements
1918
+ 32. **`$selection` only inside repeat with selection** — throws error if used outside
1919
+ 33. **CSS hover for color/opacity, Motion hover for transforms** — automatic detection, no manual choice needed
1920
+ 34. **CSS hover only works on supported primitives** — `box`, `text`, `stack`, `grid`, `scroll`, `button`, `touchable`, `table`. Other types silently ignore className. `mythik validate` catches this
1921
+ 35. **Don't use `overflow: hidden` with `box-shadow` hover** — overflow clips the shadow. Use `filter: brightness()` or remove overflow
1922
+ 36. **Toasts render automatically** — `showNotification` writes to state, the renderer picks it up. No need to add a `toast-container` element unless you want custom positioning
1923
+ 37. **Device context is auto-tracked** — viewport, platform, orientation, OS color scheme write to `/ui/device/*` automatically. Use `$breakpoint` for responsive values, `$platform` for cross-platform branching, `$state` for orientation detection
1924
+ 38. **Use `forms` for coordinated validation** — form-level `isValid`, cross-field rules, submit gating. Use inline `checks` on inputs for standalone fields without form coordination
1925
+ 39. **Use `$auth` for user data, not `$state`** — `{ "$auth": "email" }` is safer (whitelisted fields only) and more stable than `{ "$state": "/auth/user/email" }`. `$auth` blocks access to tokens/passwords even if somehow in state
1926
+ 40. **Auth headers are auto-injected** — specs don't need to specify auth headers in `fetch`/`submitForm`. The framework injects Bearer tokens automatically for configured `authDomains`
1927
+ 41. **Login screen is a normal spec** — build login forms with regular primitives (input, button). The `login` action connects to the auth provider. No special components needed. Use `$bindState: "/login/username"` and `"/login/password"` for form fields. The `login` action receives `{ email: "$state /login/username", password: "$state /login/password" }`
1928
+ 42. **Never trust client auth as security** — `AppAuthConfig` is UX routing (hide screens by role), not server-side security. Always validate tokens on the backend
1929
+ 43. **Use `roleAccess` for centralized access control** — when defined, it's the sole source of truth. Roles not listed get zero access. `ScreenDefinition.roles` is only used when `roleAccess` is absent
1930
+ 44. **loginScreen is always accessible** — exempt from `protectedScreens`, even with `["*"]`. Prevents infinite redirect loops
1931
+ 45. **Sidebar auto-refreshes on auth changes** — `MythikApp` subscribes to `/auth/user/*` and `/auth/isAuthenticated` state changes and calls `refreshScreenList()` automatically. No manual call needed after login/logout
1932
+ 46. **Login screen renders fullscreen without layout** — `MythikApp` automatically renders the login screen WITHOUT the app layout (no sidebar, no header) when the user is not authenticated. After login, the full layout appears reactively. No `visible` conditions needed on the sidebar
1933
+ 47. **Logout clears login form automatically** — the `logout` action clears `/login/*` state before navigating to the login screen. Credentials never persist after logout. No `statePolicy: "reset"` needed on the login screen for this (though it doesn't hurt)
1934
+ 48. **Auth errors are silent in production** — auth action errors (login, logout, refreshSession) are logged to console only in development (`NODE_ENV !== 'production'`). In production, they're suppressed to prevent stack trace leakage to attackers. Errors still appear in the UI via `/auth/error` state
1935
+ 49. **Use `createAuthSpecStore` for authenticated spec loading** — when specs are served by a Mythik server with auth, use `createAuthSpecStore(API_URL)` from `mythik-react` instead of a plain specStore. It auto-injects Bearer tokens for protected screen loading. MythikApp syncs the token from AuthEngine automatically
1936
+ 50. **Input `type` not `inputType`** — the input primitive accepts `type: "password"`, not `inputType`. The CLI validates prop names and warns on unknown props with "did you mean?" suggestions (Levenshtein matching)
1937
+ 51. **Use `mythik push` for spec creation** — no seed scripts, no .ts spec files. Write JSON, push via CLI, modify with patches. All spec operations go through the CLI
1938
+ 52. **Use `uploadFile` for file uploads** — not custom `fetch` with FormData. The action handles validation, progress tracking, retry, and state management
1939
+ 53. **Set `accept` and `maxSize` on every file-upload** — framework defaults (10MB, any type) protect, but explicit is better for UX and security
1940
+ 54. **`autoUpload: true` (default) for simple cases, `false` for multi-field forms** — auto-upload sends files immediately on selection. Manual mode lets users select files first, then upload all together on form submit
1941
+ 55. **File URLs go to `target` path — don't read from `/ui/uploads/*`** — `/ui/uploads/{elementId}/files` is internal state for the primitive (progress, preview). The spec author reads the `target` path for the final URL(s)
1942
+ 56. **Auto-skeleton activates when spec has `fetch` in initialActions + loading + empty data** — zero config needed. Set `skeleton: false` on elements that show static content. Set `autoSkeleton={false}` on MythikRenderer to disable entirely
1943
+ 57. **Use `export` action for data downloads, not custom fetch** — format `"csv"` is always available (built-in). For `"xlsx"` or `"pdf"`, register an ExportAdapter via `exportAdapters` prop on MythikRenderer
1944
+ 58. **Export columns support formatting** — `format: "currency"` with `formatOptions: { currency: "USD", locale: "en-US", decimals: 2 }`. Same Intl formatters as `$format` expression handler
1945
+ 59. **Use `variant` prop for consistent component styling** — don't copy style objects between elements. Define variants in `tokens.components.{type}.{variant}` and reference with `"variant": "primary"`
1946
+ 60. **Element-level style/hover/active overrides variant** — variant is always the base. For one-off customizations, add explicit style/hover on the element. For reusable customizations, define a new variant
1947
+ 61. **Use `$path` references in variant definitions** — `$colors.primary` resolves against active tokens so dark mode works automatically. No need for separate dark variant definitions unless the dark change isn't just a token swap
1948
+ 62. **AppSpec patches use `/layout/elements/` not `/elements/`** — Screen spec elements are at `/elements/{id}`. AppSpec layout elements are at `/layout/elements/{id}`. Use dot-notation with `mythik elements` for inspecting non-element sections (tokens, screens, etc.)
1949
+ 63. **AppSpec is filtered without Bearer token** — `GET /api/app/:id` strips `roleAccess` and `protectedScreens` from the response when no valid Bearer token is present. This prevents information disclosure of the authorization model. With a valid Bearer, the full AppSpec is returned. The endpoint never returns 401 — only the payload changes
1950
+ 64. **CLI warns on unknown prop names** — `mythik push` and `mythik validate` check prop names against known schemas for all 38 primitives. Unknown props generate warnings (not errors) with Levenshtein suggestions: `⚠ unknown prop "inputType" for type "input" — did you mean "type"?`. Warnings don't block saves
1951
+ 65. **Use `audit` config for automatic change tracking** — endpoint-level config that auto-injects username and timestamp into CRUD INSERT and UPDATE operations. Column names are configurable per table. All fields are optional — only configured fields are injected. Audit values override client-sent values (prevents spoofing)
1952
+ 66. **Audit `timezone` for correct local timestamps** — set `"timezone": "America/El_Salvador"` (IANA string) in audit config. Without timezone, timestamps are UTC. Use timezone when the database stores local time. The framework converts using `Intl.DateTimeFormat` — works regardless of server location
1953
+ 67. **CRUD queries are trigger-safe** — INSERT uses `SCOPE_IDENTITY()` + SELECT instead of `OUTPUT INSERTED.*`. UPDATE uses SELECT after the update. Both patterns are compatible with SQL Server tables that have triggers enabled
1954
+ 68. **CustomJWTProvider maps response paths explicitly** — `tokenPath`, `refreshTokenPath`, and `userPath` are dot paths against the full login/refresh response. `rolePath` and `rolesPath` are compat dual: plain keys (`"role"`, `"roles"`) resolve inside the extracted user object, dotted paths (`"user.role"`, `"data.user.role"`) resolve against the full response. If no role/roles are found, `defaultRole` is used and development logs a warning. HTTP errors read `response.error.message` first, then `response.message`
1955
+ 69. **ApiSpec is pure declarative — no connection, no secrets** — `createServer` receives database connection and JWT secret in `MythikServerConfig`, not in the ApiSpec. The ApiSpec only contains endpoints, catalogs, auth model (provider, policies, scopeFilter), and audit config. This allows the ApiSpec to be stored safely in the database
1956
+ 70. **Server loads api-spec from SpecStore** — `createServer({ spec: { store: myStore, id: 'my-api' }, database: {...}, auth: {...} })`. Supports three modes: file path (string), ApiSpec object (testing), or `{ store, id }` (database). The server never owns a SpecStore — the caller provides one
1957
+ 71. **SpecStore table is configurable** — `new SqlServerSpecStore({ ..., table: 'api_specs' })` (imported from `mythik/server` — Node-only) and `new SupabaseSpecStore({ ..., table: 'api_specs' })` (imported from `mythik` — browser-safe). Default: `'screens'`. Enables separate tables for frontend specs and backend specs with different DB permissions
1958
+ 72. **Api-specs are never served to the browser** — `GET /api/screens/:id` and `GET /api/app/:id` return 404 for documents with `type: "api"`. Prevents information disclosure of table names, SQL queries, and auth config
1959
+ 73. **CLI operates on api-specs automatically** — `mythik validate my-api`, `mythik pull my-api`, `mythik push my-api` detect `type: "api"` and use the apiHandler. Manifest shows catalogs, endpoints, auth config. Elements support dot-notation: `mythik elements my-api endpoints.records-crud`
1960
+ 74. **Use `mythik contract` for frontend↔backend validation** — `mythik contract --app app-demo --api records-api`. Cross-validates screen fetch URLs against api-spec endpoints. Supports multiple api-specs (comma-separated). Use `--api-table` when api-specs are in a separate table. Use `--base-url` to strip host from URLs. Use `--json` for CI/CD integration
1961
+ 75. **Contract validates 4 rules** — (1) endpoints-exist: fetch URLs must match api-spec endpoints/catalogs (error). (2) fields-valid: POST/PUT body fields must be in CRUD insertable/updatable (error). (3) params-match: query params must match endpoint param definitions (warning). (4) permissions-consistent: UI roleAccess must be consistent with API policies (warning). All rules include Levenshtein "did you mean?" suggestions
1962
+ 76. **Contract permission check is unique** — no other framework can detect "Role X can access screen Y but API denies the action." This is only possible because both frontend (roleAccess) and backend (policies) are declarative JSON that can be cross-referenced statically. In code-based systems, UI visibility and API permissions live in separate layers
1963
+ 77. **Input `on.submit` fires on Enter key** — add `on: { submit: { action: "login", params: {...} } }` to an input element. Pressing Enter triggers the action. Use for login forms and search fields
1964
+ 78. **`_motion.whileTap` and `_motion.whileHover` work in specs** — Motion animations from `_motion` now forward `whileTap` and `whileHover` to Framer Motion. Use `"Infinity"` (string) for `repeat` in JSON — the renderer converts it to JS `Infinity` automatically. Enables infinite looping animations declared in specs
1965
+ 79. **Contract deduplicates findings** — identical findings (same rule + screen + level + message) are collapsed into one with a `count` field. CLI shows `(×N)` when count > 1. Prevents noise from repeated fetch references to the same missing endpoint
1966
+ 80. **Contract handles template domain URLs** — fetch URLs like `http://${authDomain}/api/items` are normalized to `/api/items` before matching. The extractor detects `http(s)://:param/path` patterns after template replacement and strips the domain portion. Works with any variable name and optional port
1967
+ 81. **Contract --base-url is explicit only** — there is no auto-detection from authDomains. If screen specs use absolute URLs and no `--base-url` is provided, the engine emits a warning: "Absolute URLs detected... Use: mythik contract --base-url http://your-server:port". Template domain URLs (containing `${`) do not trigger this warning since they're already normalized
1968
+ 82. **VersionedSpecStore extends SpecStore** — adds `saveVersion(id, doc, meta)`, `loadVersion(id, version)`, `listVersions(id, limit?)`, `currentVersion(id)`. Backward compatible — `load(id)` still returns the current spec. Versioning is opt-in; existing code works without changes
1969
+ 83. **Version history uses patch-chain with snapshots** — v1 stores the full spec. Subsequent versions store RFC 6902 patches. Every N versions (configurable, default 10) a full snapshot is stored. Reconstructing version X = nearest snapshot + apply patches. `computePatches(before, after)` generates patches; `applyPatches` (existing) applies them
1970
+ 84. **EnvironmentStore manages version pointers** — environments (dev, uat, prod, any custom name) are pointers to versions: `{ name, version, promotedAt, promotedBy }`. Created on-demand. `setEnvironment(specId, env, version, author)` creates or moves a pointer. Loading by environment = resolve pointer → loadVersion
1971
+ 85. **Structural diff is semantic with before/after values** — `computeStructuralDiff(before, after)` produces `StructuralChange[]` with kinds: element-added, element-removed, prop-changed, action-changed, section-changed. Type-aware: screen specs diff elements/props, api specs diff endpoints/catalogs at item level, app specs diff navigation/tokens. Shows actual values: `prop content: "Send" → "Submit"`, `orderBy: added "year DESC"`, `orderBy: removed (was "year DESC")`. Summary: `+2 elements, ~3 changes, -1 element`
1972
+ 86. **Promote gate validates across environments before moving pointers** — `runPromoteGate({ specIds, fromEnv, toEnv, store, envStore, apiIds? })` runs 3 validation layers: (1) spec validation — structural rules, always available; (2) cross-screen consistency — referenced screens exist in destination or batch, always available; (3) contract validation — against target env api-spec, only when `--api` provided. Supports individual, app-complete, and batch-selective modes. Atomic: all specs pass or none are promoted
1973
+ 87. **Promote uses batch-aware contract validation** — when an api-spec is in the promote batch, contract validation runs against the batch version (source env), not the destination version. This prevents false alarms when promoting screens + updated api-spec together. When api-spec is NOT in batch, validates against destination env version
1974
+ 88. **Rollback creates new version, never rewrites history** — `executeRollback(specId, toVersion)` creates vN+1 with the content of the target version. All intermediate versions are preserved. Impact analysis shows lost changes with author attribution and affected environments. Moving an environment pointer is a separate operation via `envs --set`
1975
+ 89. **Lazy bootstrap for existing specs** — first versioned save on an existing spec with no history automatically creates v1 (snapshot of current spec), then saves the change as v2. No migration script needed. Specs without history continue working via base `SpecStore.load()`
1976
+ 90. **Use `navigateScreen` and `goBackScreen` in specs** — `navigateScreen` and `goBackScreen` are registered by MythikApp and call the AppEngine directly. The built-in `navigate` and `goBack` only set state intents. Always use `navigateScreen`/`goBackScreen` in specs for navigation that works. Example: `{ "action": "goBackScreen" }` goes back to the previous screen in the navigation history, regardless of which screen navigated to the current one
1977
+ 91. **CLI `--table` flag overrides store table** — all commands accept `--table <name>` to read/write from a different table. Use `--table api_specs` to operate on api-specs. The flag overrides `config.sqlserver.table` or `config.supabase.table`. Environment and version tables are NOT affected by `--table` — they always use their fixed names (`screen_environments`, `screen_versions`)
1978
+ 92. **`push` and `patch` version automatically with `--author`** — when `--author` is provided, both commands use `VersionedSpecStore.saveVersion()` instead of `SpecStore.save()`. The version includes author, source type (`push`/`patch`), and optional `--description`. Without `--author`, commands work as before (no versioning). Example: `mythik patch screen-id --author alice --description "Fixed layout"`
1979
+ 93. **`mythik history` shows inline diffs** — each version in the history output shows the actual changes (before/after values), not just a summary. Uses `computeStructuralDiff` between consecutive versions. Example output: `~ element "btn" prop content: "Send" → "Submit"`
1980
+ 95. **`ai-context.md` is the AI-optimized spec reference** — compressed from this reference-doc (1145 lines vs 2658). Use ai-context.md for spec generation, reference-doc for full human reference. Validated via agent-based testing with progressive difficulty levels (L1-L4). Test scenarios in `../ai-context-test-scenarios.md`, results in `../ai-context-test-results.md` (framework-dev, not part of consumer publish surface)
1981
+
1982
+ 94. **SqlServerVersionedSpecStore available** — `resolveVersionedStore` supports `sqlserver` store type. Creates `SqlServerVersionedSpecStore` (specs + version history) + `SqlServerEnvironmentStore` (environment pointers). Requires `screen_versions` and `screen_environments` tables — create with `setup-versioning.ts`
1983
+ 96. **SupabaseVersionedSpecStore available** — `resolveVersionedStore` supports `supabase` store type. Uses PostgREST REST API (no `@supabase/supabase-js` dependency). Same snapshot+patches pattern as SqlServer. Environment upsert uses `on_conflict=screen_id,environment` for PostgREST compatibility. Requires `screen_versions` and `screen_environments` tables created in Supabase dashboard
1984
+ 97. **`variant` is a universal prop** — any primitive can use `variant` when component variants are defined in `tokens.components.{type}.{variant}`. The validator accepts `variant` on all primitives (via `COMMON_PROPS`). The render engine resolves variants before passing props to the primitive
1985
+ 98. **Use DNA seeds for app identity** — define `tokens.dna` in AppSpec with 1-8 seed values. The framework derives all visual tokens (colors via OKLCH tonal palette, shape, typography, spacing, elevation, motion, opacity) plus auto dark mode. No manual color palette or radius scale needed — DNA generates it from `{ "primary": "#0D9488" }`
1986
+ 99. **Three-layer token resolution** — Framework DEFAULTS (always present) → DNA derivation (if `dna` seed exists) → Manual overrides (explicit `tokens.*` values always win). Specs without tokens use defaults. Specs with only DNA get full derived identity. Specs with DNA + overrides get derived base with surgical customization
1987
+ 100. **`$token` auto-converts elevation to CSS** — `{ "$token": "elevation.md" }` returns a CSS `boxShadow` string on web (e.g., `"0px 4px 12px rgba(0,0,0,0.15)"`). On RN, primitives handle elevation internally via native shadow props. No manual conversion needed
1988
+ 101. **`updateTokens` for runtime token changes** — `{ "action": "updateTokens", "params": { "dna": { "primary": "#F97316" } } }` hot-swaps all tokens without page reload. All primitives re-render with new values. Numeric DNA seeds > 1 are auto-normalized (divided by 100 for 0-100 slider compatibility)
1989
+ 102. **Font loading is the project's responsibility** — DNA derives font family names (Inter, DM Sans, Playfair Display, Georgia, Source Sans) but does NOT load them. Web: include Google Fonts link or `@font-face`. RN: use `expo-font`. Without loading, browser falls back to system fonts
1990
+ 103. **`useDesignTokens` replaces `useThemeColors` in primitives** — all 35 migrated primitives (18 web + 17 RN) use `useDesignTokens` internally. It extracts colors, shape, typography, spacing, elevation, motion, and opacity with framework defaults as fallback. `useThemeColors` is deprecated but still works as wrapper
1991
+ 104. **`mythik tokens` inspects DNA derivation** — `mythik tokens --dna '{"primary":"#0D9488"}' --json` shows the full resolved token set. Use for debugging and verifying what DNA generates before applying to a spec
1992
+ 105. **Formality drives 5 font tiers + continuous properties** — Body: Inter (0) → Space Grotesk (0.25) → Source Sans 3 (0.5) → Lora (0.75) → Merriweather (1.0). Heading: Inter → Space Grotesk → DM Sans → Lora → Playfair Display. Font names include CSS fallbacks (e.g., `'Inter', sans-serif`). Additionally, formality continuously varies `letterSpacing` (0 → 0.03em), `headingLetterSpacing` (0 → -0.02em), `lineHeight` ratio (1.45 → 1.6), and bold weight (700 → 800)
1993
+ 106. **Neutral palette is visually distinct per mode** — `warm` uses amber hue (55°), `cool` uses blue (250°), `natural` uses primary hue. Chroma is 0.025 (natural) / 0.035 (warm/cool) — strong enough to be visible in backgrounds, borders, and surfaces
1994
+ 107. **`Element.key` forces remount on value change** — Add `key` to any element with a dynamic expression (e.g., `{ "$template": "preview-${/internal/tokenVersion}" }`). When the resolved value changes, React unmounts and remounts the element, re-triggering mount animations. Used by DNA Playground to re-animate preview on Apply DNA
1995
+ 108. **Input supports `type: "color"`** — Renders native OS color picker. Use with `$bindState` for interactive color selection in specs
1996
+ 109. **Slider label is display-only** — The slider primitive renders the `label` prop as-is without appending the current value. Use `$template` in the label to include the value if desired (e.g., `"label": { "$template": "Roundness: ${/dna/roundness}%" }`)
1997
+ 110. **`--author` activates CLI versioning** — `mythik push` and `mythik patch` without `--author` write directly to the `screens` table with NO version history. With `--author ai-agent`, they use `VersionedSpecStore` which writes to both `screens` AND `screen_versions`, enabling `history`, `diff`, and `rollback` commands. Always use `--author` during development
1998
+ 111. **Identity System — `tokens.identity` controls visual identity beyond DNA** — DNA controls continuous/color values. Identity controls categorical dimensions: `surface` (how containers render), `radiusPattern` (corner shape), `typographyHierarchy` (heading scale), `labelStyle` (label formatting), `textDecoration` (heading decoration), `depth` (shadow intensity 0-1), `shadowAngle` (shadow direction 0-360°), `colorScheme` (light/dark/colored polarity), `colorWeight` (where color appears), `accentApplication` (where accent appears). Set via `tokens.identity` in AppSpec or screen Spec. Lint validates deep identity enum/range values; for example use `colorScheme: "light-surface"`, not `"light"`
1999
+ 112. **Surface Treatment — 6 types transform all containers** — `identity.surface`: `elevated` (shadow+border, material), `flat` (color blocks only, no borders/shadows), `outlined` (borders only, transparent bg, Linear-like), `glass` (backdrop-filter blur, semi-transparent), `bold` (thick 2-3px borders, brutalist), `neo` (neumorphic inset+outset shadows). Every input, button, card, modal, select, textarea, checkbox, accordion, table adapts automatically
2000
+ 113. **Surface styles are explicit — every property set, no implicit values** — All 6 surface types define `border` and `boxShadow` explicitly for every component category. Properties are `'none'` when not used, never omitted. This prevents browser defaults from showing (e.g., native button borders in flat mode)
2001
+ 114. **`resolveSurfaceStyles()` returns structured platform-neutral objects** — Returns `StructuredSurfaceStyles` with 6 component categories: `card`, `input`, `inputFocus`, `buttonPrimary`, `buttonSecondary`, `modal`. Each is a `StructuredSurfaceStyleProps` with `backgroundColor?`, `color?`, `border?: BorderDef`, `borderTop/Right/Bottom/Left?: BorderDef`, `shadows: ShadowDef[]`, `blur?: BlurDef`, `backgroundOpacity?`. NO CSS strings — data only. Platform serializers convert: `surfaceToCSS()` for web (React/Vue/Angular/Svelte), `surfaceToRN()` for React Native. Core stays platform-neutral
2002
+ 115. **Surface-aware focus per surface type** — `inputFocus` in SurfaceStyles. Elevated/outlined: 1px border primary + ring. Flat: no border, ring only. Bold: 2px border primary + ring (preserves thickness). Neo: primary-tinted inset shadow + ring. Glass: semi-transparent primary border + ring. Primitives use `t.surface.inputFocus` instead of hardcoded focus styles
2003
+ 116. **Box primitive gains `surface` prop** — `{ "type": "box", "props": { "surface": "card" } }` applies identity surface styles (border, shadow, bg, borderRadius). Without `surface` prop, box renders as plain layout div. Use `surface="card"` for card-like containers, `surface="modal"` for overlay panels. AI should mark card-like boxes with this prop
2004
+ 117. **Select dropdown uses `surface.modal` not `surface.card`** — Dropdowns are overlays that need opaque backgrounds for readability. In outlined mode, `card` has transparent bg which breaks dropdowns. Modal always has solid bg regardless of surface type
2005
+ 118. **Flat inputs differentiate by background color, not borders** — Flat spec: "NO borders, differentiation through background color blocks only." Inputs and buttonSecondary use `colors.background` (#f8fafc off-white) instead of `colors.surface` (#ffffff) to remain visible without borders
2006
+ 119. **Radius Pattern — 11 asymmetric corner options** — `identity.radiusPattern`: `all` (default), `top`, `bottom`, `left`, `right`, `diagonal` (TL+BR), `inverse-diagonal` (TR+BL), `single` (TL only), `single-tr`, `single-bl`, `single-br`. Applied via `t.radius(baseValue)` helper in all primitives. `resolveRadiusPattern()` converts to CSS border-radius string
2007
+ 120. **Typography Hierarchy — 6 heading scales** — `identity.typographyHierarchy`: `dramatic` (3×, 800w), `uniform` (1.3×, 600w — Notion-like), `editorial` (2.2×, 700w, -0.02em tracking), `display` (4×, 900w, -0.03em — magazine hero), `mono` (1.5×, monospace font), `contrast` (2.5×, 300w ultra-thin — luxury/Apple). Applied to `<Text variant="heading">`
2008
+ 121. **Label Style — 3 modes on all form labels** — `identity.labelStyle`: `normal`, `uppercase` (textTransform + letterSpacing 0.08em), `accent-colored` (label text in accent color). Applied to labels inside input, textarea, select, and checkbox primitives — not just `<Text variant="label">`
2009
+ 122. **Text Decoration — 6 heading effects, multi-select** — `identity.textDecoration`: `stroke` (webkit-text-stroke), `underline-accent` (3px bottom border, accent color), `highlight` (accent bg tint behind text), `overline` (3px top border), `shadow` (3px hard offset, no alpha — solid accent). Supports array for combinations: `["underline-accent", "shadow"]`. `resolveTextDecorations()` merges styles from all selected decorations
2010
+ 123. **Custom checkbox is surface-aware** — Replaced native `<input type="checkbox">` with custom div+SVG. Unchecked: uses `t.surface.input` styles. Checked: fills with primary, preserves surface border (bold keeps 2px, neo keeps shadow). Focus uses `t.surface.inputFocus`. Hidden input preserved for accessibility
2011
+ 124. **Elevation and border resolvers ready but not yet consumed** — `resolveElevationStyle()` (diffuse/solid/color/none) and `resolveBorderStyle()` defined, tested, exported, values in `t.identity.*`. Primitives don't consume them yet — reserved for future phases when elevation identity overrides surface defaults
2012
+ 125. **`colors.background` is the STATIC base page background** — Distinct from `colors.surface` (card/component bg). Returns `#f8fafc` light gray (or dark-mode equivalent via `/preferences/theme`), but does NOT respond to preset `identity.colorScheme` changes. Safe for light-only apps. For preset-aware layouts (apps that support dark-surface or colored-surface presets), use `backgroundCSS` instead — see rule 155. Visible failure mode: dark preset with layout using `colors.background` produces a light frame around dark content
2013
+ 126. **`updateTokens` does deep merge — partial updates work** — Calling `updateTokens({ dna: { roundness: 50 } })` merges with previously-applied tokens instead of replacing them. Previous primary color, surface settings, etc. persist. Enables live playground controls where each slider updates only its dimension
2014
+ 127. **`deepMergeTokens` exported from core** — `import { deepMergeTokens } from 'mythik'`. Recursively merges objects (arrays and non-objects replace, objects deep-merge). Used internally by `updateTokens`
2015
+ 128. **Depth and angle are serializer concerns, not data concerns** — `identity.depth` (0-1) and `identity.shadowAngle` (0-360°) are NOT in `StructuredSurfaceStyleProps`. Shadows store raw `magnitude`/`blur`/`opacity`. The serializer (`surfaceToCSS`/`surfaceToRN`) applies `depthScale()` and sin/cos angle rotation when converting to platform-specific format. This keeps the structured data pure and reusable
2016
+ 129. **`resolveIdentity<T>()` — unified orchestrator** — Single function in core orchestrates: resolveSchemeColors → generateTonalStep → resolveSurfaceStyles → serializer(T) → resolveColorWeight → resolveRadiusPattern. Accepts a `SurfaceSerializer<T>` function — each platform passes its own (surfaceToCSS, surfaceToRN). Returns `{ schemeColors, surface: T, colorWeight, radius }`. Web/RN hooks call this one function instead of 6+ manual steps
2017
+ 130. **Color Scheme — surface polarity with configurable layers** — `identity.colorScheme`: `light-surface` (default, pass-through), `dark-surface` (uses `modes.dark` from DNA), `colored-surface` (3 configurable tonal layers from primary palette). `colored-surface` uses `identity.coloredSurfaceLayers: { background: 25, surface: 45, primitive: 65 }` — tonal steps from the primary OKLCH palette. `generateTonalStep()` produces colors at any step 0-100. Default 25/45/65 validated across 8 hues. Focus ring uses accent (not primary) in colored-surface to avoid invisible ring on primary background
2018
+ 131. **`t.colors` stays original palette, scheme only affects surfaces** — `t.colors.text` always returns the original light palette text color. Surface styles (`t.surface.card`, etc.) use scheme-adjusted colors internally. This separation ensures UI controls (selects, labels) remain readable while the preview/app content adapts to the scheme. Preview elements use `$token: schemeColors.*` for scheme-aware text
2019
+ 132. **Glass fix — derives rgba from actual surface color** — `hexToRgba(c.surface, opacity)` replaces hardcoded `rgba(255,255,255,...)`. Dark scheme + glass = semi-transparent dark, not semi-transparent white. All glass card, input, button, modal use `hexToRgba` for background and border
2020
+ 133. **Color Weight — WHERE color appears in layout** — `identity.colorWeight`: `monochrome` (all neutral), `branded-nav` (primary navbar, white text), `gradient-hero` (primary→accent gradient hero section), `ambient` (subtle primary tint on sections via rgba), `dark-native` (dark nav using modes.dark). `resolveColorWeight()` returns `{ navBg, navText, sectionBg, heroBg, heroGradient }`. Layout tokens available via `$token: colorWeight.navBg`
2021
+ 134. **Accent Application — where accent color appears** — `identity.accentApplication`: `buttons` (boolean — buttonPrimary uses accent), `navItems` (boolean — highlight active nav), `cardLine` (array of positions — accent border on cards), `links` (boolean — link color), `backgrounds` (boolean — section bg uses accentLight), `iconContainers` (boolean). `cardLine` supports multi-select: `["top", "left"]` for L-shape accent border. Applied inside `resolveSurfaceStyles` via `SurfaceOptions.accent/cardLine/accentButtons`
2022
+ 135. **Surface styles include color: c.text for CSS inheritance** — All 6 surface types set `color: c.text` on card, input, and modal categories. Child primitives use `color: 'inherit'` to pick up the parent's scheme-adjusted color. This makes dark/colored surface text automatically correct via CSS inheritance
2023
+ 136. **Text/Input/Select/Textarea/Checkbox primitives use `color: 'inherit'`** — These primitives no longer hardcode `t.colors.text`. They inherit text color from their parent container's surface style. Standalone usage inherits from body/page default color. Surface containers (Box, Accordion, Modal) set the correct color
2024
+ 137. **`injectColorTokens` in factory.ts** — After `resolveDeepTokens`, the factory computes `colorWeight` and `schemeColors` and injects them into the resolved token tree. This makes `$token: "colorWeight.navBg"` and `$token: "schemeColors.text"` available in specs
2025
+ 138. **colorWeight always returns heroBg + heroGradient** — Non-gradient modes return `'transparent'`/`'none'` instead of undefined. Prevents $token resolution errors when specs reference `colorWeight.heroGradient` regardless of active weight mode
2026
+ 139. **`surfaceToCSS` emits `border: 'none'` explicitly** — When a surface has no border (flat, neo, glass buttons), the CSS serializer outputs `border: 'none'` instead of omitting the property. This resets browser defaults (native inputs have 2px inset border). Without explicit 'none', flat/neo inputs show unwanted browser borders
2027
+ 140. **`inputFocus` has NO backgroundColor** — Focus styles only contain border and shadow changes, never backgroundColor. If inputFocus included backgroundColor, it would overwrite the checkbox's primary bg when checked+focused, making the white checkmark invisible on white background
2028
+ 141. **BlurView support in React Native** — When `surface === 'glass'`, the RN serializer passes `blur: { radius }` through to surface props. RN primitives (accordion, input, modal, select, textarea) detect blur and wrap content in `<BlurView>` from `expo-blur`. `toViewStyle()` helper strips `blur` and `focusRing` from surface props before spreading into RN style objects
2029
+ 142. **Glass border uses semi-transparent rgba** — Glass surface borders use `hexToRgba(c.surface, 0.2)` for cards/modal/secondary and `hexToRgba(c.surface, 0.15)` for inputs. NOT opaque surface color. Glass inputFocus border uses `c.primary + '80'` (50% opacity hex suffix)
2030
+ 143. **Identity icons — weight default + container from tokens** — `identity.icons.weight` applies as default on all icon primitives. Spec `weight` prop overrides. `identity.icons.container` ('none'|'circle'|'square'|'rounded-square') auto-wraps icons with colored background. `container={false}` in spec suppresses. `containerColor`: 'primary'|'accent'|'muted'|'surface'
2031
+ 144. **Identity images — corners, overlay, border from tokens** — `identity.images.corners` applies as default borderRadius ('match-card' uses card radius, 'circle'=50%, 'square'=0, 'rounded'=8px). `identity.images.overlay` adds gradient-bottom or color-tint overlay div. `identity.images.border` adds 1px border. Spec `style.borderRadius` or `overlay="none"` overrides
2032
+ 145. **Gradient text on headings** — `identity.gradients.text = true` applies `background-clip: text` with primary→accent gradient on all `<Text variant="heading">`. Spec `style.color` overrides (gradient deactivates when explicit color set). Web-only (RN deferred — requires expo-linear-gradient)
2033
+ 146. **Gradient buttons** — `identity.gradients.buttons = true` applies `linear-gradient(135deg, primary, accent)` on primary buttons. Spec `style.background` overrides. Web-only (RN deferred)
2034
+ 147. **Background treatment — LayerBackground v2** — `identity.background` is `{ color?: string, layers?: LayerConfig[] }` consumed by `<BackgroundStack>` mounted at the root of `MythikRenderer` when present. Layer kinds: `'solid' | 'gradient' | 'pattern' | 'grain' | 'image' | 'blobs'`, each carrying `LayerCommonProps` (`opacity?`, `blendMode?`, `zIndex?`). `resolveBackgroundLayers(bg)` → `LayerSpec[]` → `<BackgroundLayer>` per kind. Blob layers route to `<BlobLayer>` when palette is available. Legacy `resolveBackgroundCSS`, `resolveBlobStyles`, `BackgroundConfig`, `BlobStyle`, and `tokens.backgroundCSS` are not part of the public contract.
2035
+ 148. **Blob layer v2 — preset + explicit forms, motion dimensions** — `BlobV2Config` carries `preset: 'organic-duo'|'organic-trio'|'circle-pair'` + `palette: Array<'primary'|'accent'|hex>` + `motion: 'drift-gentle'|'drift-fluid'|'drift-snappy'|'static'` for framework-curated compositions, OR `blobs: BlobInstance[]` for explicit declarations. Each `BlobInstance` carries `shape` (6 curated + `'custom-svg'` with `path`+`viewBox`), `position`, `size`, `color`, optional `opacity`/`blur`/`rotation`/`motion`. Motion breaks into three independently composable dimensions (`drift`, `rotate`, `scale`) that `resolveBlobLayer` converts to `ElementAnimations.ambient` arrays. `useShapeAnimations` (web: CSSOM keyframes singleton; RN: Reanimated `useAnimatedProps` + `HARD_PER_TRIGGER` SV pool) drives them at render time
2036
+ 149. **Coordinated page entrance** — do not emit legacy `identity.motionEntrance` or `identity.motionHover`. Entrance animations live in the animation engine: app-wide defaults use `tokens.identity.animations.mount`, while template-level and element-level overrides use `animations.mount` with a recipe reference (e.g., `{ mount: { recipe: 'fade-up' } }`). See rules 180-193 for the engine.
2037
+ 150. **Gradient interpolation in OKLCH** — All gradient text and gradient buttons use `linear-gradient(in oklch, ...)` for vibrant transitions. sRGB interpolation crosses through desaturated midpoints (purple→gold becomes muddy brown); OKLCH maintains saturation through the hue arc. Graceful degradation on older browsers
2038
+ 151. **Gradient modes: vibrant, soft, muted** — `identity.gradients.text`, `identity.gradients.buttons`, and `identity.gradients.cards` accept `boolean | 'vibrant' | 'soft' | 'muted'`. For text and buttons: Vibrant = OKLCH primary→accent. Soft = OKLCH primaryLight→accentLight (subtle). Muted = sRGB primary→accent (desaturated center, luxury/editorial aesthetic). For cards: Vibrant = 33% primary alpha (dramatic). Muted = 20% primary alpha (balanced default). Soft = 8% primary alpha (subtle kiss). `true` maps to vibrant for text/buttons, muted for cards
2039
+ 152. **Explicit accent hex preserved as-is** — When `dna.accent` is set explicitly, `deriveDna` uses the exact hex for `colors.accent` instead of passing through `generateTonalPalette` step 60. The palette is still generated (for `accentLight`), but the primary accent color is the user's exact choice
2040
+ 153. **`setIconRenderer` replaces `overridePrimitive` for icons** — `plugins.setIconRenderer(Component)` registers a custom icon renderer. The framework's `icon.tsx` handles identity wrapping (container, weight default) while the registered component renders the actual icon. `overridePrimitive('icon', ...)` still works but bypasses identity features
2041
+ 154. **Do not emit Box `backgroundBlobs`** — Box `backgroundBlobs` and legacy per-element blob rendering are not part of the public primitive contract. App-level background lives exclusively at `tokens.identity.background` (rule 147) and mounts at MythikRenderer via `<BackgroundStack>`. Box is a pure surface-styled wrapper; primitives do not rely on `dangerouslySetInnerHTML` for keyframe injection.
2042
+ 155. **Do not emit `$token: "backgroundCSS"`** — `$token: "backgroundCSS"` does not resolve. For preset-aware app backgrounds, set `tokens.identity.background` as a LayerBackground (rule 147) — presets update that field directly so the root `<BackgroundStack>` re-resolves on preset switch. No token alias indirection needed.
2043
+ 156. **Accordion `badge` prop** — `accordion` accepts optional `badge` prop (`string | number | boolean`). Renders as: solid 8px primary-colored dot for `true`, pill with text/number for string/number values. Hidden when `false`, `0`, `undefined`, or `''`. Use with `$state` for reactive indicators: `"badge": { "$state": "/modified/mySection" }`
2044
+ 157. **`updateTokens` `_replace` flag** — Pass `{ _replace: true, dna: {...}, identity: {...} }` to bypass `deepMergeTokens` and fully replace accumulated token state. Without `_replace`, each `updateTokens` call deep-merges onto previous values. Use `_replace` for reset-to-defaults scenarios where accumulated overrides must be cleared entirely
2045
+ 158. **`/tokens/raw` state path** — After every `updateTokens` call, the framework persists `currentRawTokens` to `/tokens/raw` in the state store. This is the accumulated raw input (pre-normalization), useful for exporting the current DNA+Identity configuration. Read via `{ "$state": "/tokens/raw" }`
2046
+ 159. **`copyToClipboard` stringifies objects** — The `copyToClipboard` action auto-stringifies object values with `JSON.stringify(value, null, 2)` before writing to `/ui/clipboard`. String and number values pass through as-is. The React renderer watches `/ui/clipboard` and calls `navigator.clipboard.writeText()` automatically
2047
+ 160. **Identity module structure** — Identity system split into `packages/core/src/design/identity/` with 8 focused modules: `types.ts` (all types/interfaces), `surface.ts` (6 surface variants), `shape.ts` (radius/border), `elevation.ts` (depth/shadow), `typography.ts` (hierarchy/decoration/labels), `color.ts` (scheme/weight/hexToRgba), `background.ts` (CSS/blobs), `motion.ts` (entrance). Barrel `index.ts` re-exports all public API. Internal helpers (`sh`, `neoShadows`, `seededRandom`, etc.) are module-private
2048
+ 161. **Border & elevation identity override** — `identity.borderWidth`, `borderStyle`, `borderColor`, `elevationStyle`, `elevationColor` override surface-produced borders/shadows via `applyBorderElevationCSS` (web) and `applyBorderElevationRN` (RN). Override is gated by two independent boolean flags: `overrideSurfaceBorders` (card+modal) and `overrideInputButtons` (input+buttonPrimary+buttonSecondary). Both default `false` — surface type controls everything until explicitly enabled. `inputFocus` is never overridden (accessibility). `borderColor` is semantic: `'neutral'|'primary'|'accent'|'text'` → resolved to hex via schemeColors. `elevationColor`: `'dark'|'primary'|'accent'` → resolved to hex (`dark`=#000000)
2049
+ 162. **Elevation styles** — `elevationStyle` controls shadow rendering: `'none'` (no shadow), `'diffuse'` (soft blur shadow, rgba), `'solid'` (no-blur offset shadow, direct color — comic book style), `'color'` (blur shadow using elevationColor in rgba). Shadow angle decomposed via sin/cos matching the surface serializer's `shadowToCSS` pattern. `depthScale()` modulates intensity
2050
+ 163. **deepResolve composes objects from multiple $state paths** — Action params and element props support nested objects where each value is a `$state` expression: `{ "dna": { "$state": "/dna" }, "identity": { "$state": "/identity" } }` → resolved to `{ dna: {...}, identity: {...} }`. Works in action params via `deepResolve` in dispatcher.ts and in element props via the unified deep resolver (`resolveDeep`) in engine.ts. No `$object` expression handler needed
2051
+ 164. **Primary color preserved as-is** — `deriveDna` uses `seed.primary` directly as `colors.primary` (the exact hex the user chose). Variants derived via relative OKLCH lightness: `primaryLight` = L+15%, `primaryDark` = L-20%. Tonal palette still generated for dark mode (step 80) and neutrals. Previously, palette step 60 was used, which destroyed naturally light colors (amber L=77% → brown L=60%)
2052
+ 165. **headingColor** — `identity.headingColor` (`'default'|'primary'|'accent'|'primary-dark'`) controls heading text color. `'default'` = inherit from container. Resolves to scheme colors. Gradient text (`identity.gradients.text`) takes precedence when active. Priority: `style.color > gradient > headingColor > inherit`. Applied in text.tsx for web and RN
2053
+ 166. **Object composition in expressions already works** — Plain objects with `$state` children resolve recursively in both action params and element props. The framework walks the object tree: if a key starts with `$` and has a handler → resolve as expression; otherwise recurse into values. This means `{ "user": { "$state": "/user" }, "prefs": { "$state": "/prefs" } }` resolves without any `$object` handler
2054
+ 167. **Presets system** — `PresetDefinition` interface in core defines the contract for curated DNA+Identity snapshots. `plugins.registerPresets(presets)` stores them, writes `/presets/available` to state (array of `{ value, label }` for dropdown consumption), and auto-registers `applyPreset` action. Framework provides tools (contract, registration, application); preset definitions live outside core. Architectural principle: framework provides tools, not opinions
2055
+ 168. **applyPreset action** — Takes `{ preset: string }` param. Looks up registered preset by ID, calls `updateTokens({ _replace: true, dna, identity })`. `'custom'` is a silent no-op (not a real preset). Unknown IDs throw. `_replace: true` ensures zero stale flags between preset switches (addresses the override flag footgun where forgetting `overrideSurfaceBorders: true` would silently ignore border values)
2056
+ 169. **Preset dropdown pattern** — Use `$bindState` on the select's `value` prop (not `$state`) for two-way binding. `on:change` reads the already-written value via `$state`. Do NOT use `$event` in action params — `$event` is not a supported expression handler. Pattern: `"value": { "$bindState": "/ui/currentPreset" }` + `"on": { "change": [{ "action": "applyPreset", "params": { "preset": { "$state": "/ui/currentPreset" } } }] }`
2057
+ 170. **Custom detection in presets** — Every control that modifies tokens should include `{ "action": "setState", "params": { "statePath": "/ui/currentPreset", "value": "custom" } }` in its `on:change` array. This switches the preset dropdown back to "Custom" when any individual value is modified after applying a preset
2058
+
2059
+ ---
2060
+
2061
+ ## Authentication
2062
+
2063
+ ### AppSpec Auth Config
2064
+
2065
+ ```json
2066
+ {
2067
+ "navigation": {
2068
+ "auth": {
2069
+ "provider": "supabase",
2070
+ "loginScreen": "login",
2071
+ "protectedScreens": ["*"],
2072
+ "roleAccess": {
2073
+ "admin": ["*"],
2074
+ "viewer": ["dashboard", "reports"]
2075
+ },
2076
+ "persistence": "local",
2077
+ "tokenRefresh": true,
2078
+ "authDomains": ["myproject.supabase.co"],
2079
+ "sessionExpiredMessage": "Your session expired, please sign in again"
2080
+ }
2081
+ }
2082
+ }
2083
+ ```
2084
+
2085
+ **`authDomains` matcher behavior:** the matcher compares the URL's hostname only — the port is stripped. `"myproject.supabase.co"` matches requests to that hostname on any port. In dev, use `["localhost"]` (NOT `["localhost:5173"]`) — the port in the entry never matches because URL hostnames never contain ports. Subdomain matching is also supported: `"example.com"` matches `api.example.com`. See `ai-context-runtime-semantics.md § 4.1` for the full contract including URL scheme filter and subdomain rules.
2086
+
2087
+ ### `$auth` Expression
2088
+
2089
+ Access authenticated user data. Whitelisted fields only — tokens/passwords are BLOCKED.
2090
+
2091
+ ```json
2092
+ { "$auth": "email" }
2093
+ { "$auth": "role" }
2094
+ { "$auth": "roles" }
2095
+ { "$auth": "isAuthenticated" }
2096
+ { "$auth": "name" }
2097
+ { "$auth": "avatar" }
2098
+ { "$auth": "id" }
2099
+ { "$auth": "metadata" }
2100
+ { "$auth": "metadata.department" }
2101
+ { "$auth": "user" }
2102
+ { "$auth": "loading" }
2103
+ { "$auth": "error" }
2104
+ ```
2105
+
2106
+ **Blocked fields (return undefined):** `token`, `accessToken`, `access_token`, `refreshToken`, `refresh_token`, `password`, `secret`, `session`, `metadata.token`, `metadata.password`
2107
+
2108
+ ### Auth Actions
2109
+
2110
+ **`login`** — authenticate via provider:
2111
+ ```json
2112
+ {
2113
+ "action": "login",
2114
+ "params": {
2115
+ "email": { "$state": "/screens/login/email" },
2116
+ "password": { "$state": "/screens/login/password" }
2117
+ }
2118
+ }
2119
+ ```
2120
+ After login: tokens stored in closure (never state), user data written to `/auth/*`, credentials cleared from state, proactive refresh scheduled. Navigates to `initialScreen` (or first accessible screen if `initialScreen` is denied for the user's role).
2121
+
2122
+ **`logout`** — clear session:
2123
+ ```json
2124
+ { "action": "logout" }
2125
+ ```
2126
+ Clears tokens, state, persistence. Broadcasts to other tabs. Navigates to loginScreen (with `statePolicy: "reset"`, form fields are cleared automatically).
2127
+
2128
+ ### Auth State Paths (read-only, auto-managed)
2129
+
2130
+ ```
2131
+ /auth/isAuthenticated → boolean
2132
+ /auth/loading → boolean
2133
+ /auth/error → string | null
2134
+ /auth/user/id → string
2135
+ /auth/user/email → string
2136
+ /auth/user/name → string | null
2137
+ /auth/user/avatar → string | null
2138
+ /auth/user/role → string (primary)
2139
+ /auth/user/roles → string[] (all roles)
2140
+ /auth/user/metadata → object | null
2141
+ ```
2142
+
2143
+ These are protected by StateGuard — specs cannot write to `/auth/*`.
2144
+
2145
+ ### Login Screen Example
2146
+
2147
+ **IMPORTANT:** Login form fields MUST use screen-namespaced state paths (`/screens/login/...`), not root-level paths (`/form/...`). Combined with `statePolicy: "reset"` on the login screen definition, this ensures credentials are automatically cleared on logout — the AppEngine clears `/screens/login` when navigating to the login screen with reset policy. Root-level paths like `/form/email` persist across logouts because nothing clears them.
2148
+
2149
+ ```json
2150
+ {
2151
+ "root": "login-page",
2152
+ "elements": {
2153
+ "login-page": {
2154
+ "type": "box",
2155
+ "style": { "display": "flex", "alignItems": "center", "justifyContent": "center", "minHeight": "100vh", "backgroundColor": { "$token": "colors.surfaceMuted" } },
2156
+ "children": ["login-card"]
2157
+ },
2158
+ "login-card": {
2159
+ "type": "box",
2160
+ "style": { "width": 400, "padding": 32, "backgroundColor": { "$token": "colors.surface" }, "borderRadius": 16, "boxShadow": "0 4px 24px rgba(0,0,0,0.08)" },
2161
+ "children": ["title", "email-input", "password-input", "error-msg", "login-btn"]
2162
+ },
2163
+ "title": { "type": "text", "props": { "content": { "$i18n": "auth.login" } }, "style": { "fontSize": 24, "fontWeight": "bold", "marginBottom": 24 } },
2164
+ "email-input": {
2165
+ "type": "input",
2166
+ "props": { "placeholder": { "$i18n": "auth.email" }, "value": { "$bindState": "/screens/login/email" } },
2167
+ "style": { "marginBottom": 12 }
2168
+ },
2169
+ "password-input": {
2170
+ "type": "input",
2171
+ "props": { "placeholder": { "$i18n": "auth.password" }, "type": "password", "value": { "$bindState": "/screens/login/password" } },
2172
+ "style": { "marginBottom": 16 }
2173
+ },
2174
+ "error-msg": {
2175
+ "type": "text",
2176
+ "visible": { "$auth": "error" },
2177
+ "props": { "content": { "$auth": "error" } },
2178
+ "style": { "color": { "$token": "colors.error" }, "fontSize": 13, "marginBottom": 12, "padding": "8px 12px", "backgroundColor": "#FEF2F2", "borderRadius": 8 }
2179
+ },
2180
+ "login-btn": {
2181
+ "type": "button",
2182
+ "props": {
2183
+ "label": { "$cond": { "$auth": "loading" }, "$then": { "$i18n": "auth.signing_in" }, "$else": { "$i18n": "auth.login" } },
2184
+ "disabled": { "$auth": "loading" }
2185
+ },
2186
+ "style": { "width": "100%", "padding": "12px 0", "backgroundColor": { "$token": "colors.primary" }, "color": "#FFFFFF", "border": "none", "borderRadius": 10, "fontSize": 15, "fontWeight": 600, "cursor": "pointer" },
2187
+ "hover": { "backgroundColor": { "$token": "colors.primaryHover" } },
2188
+ "on": {
2189
+ "press": {
2190
+ "action": "login",
2191
+ "params": {
2192
+ "email": { "$state": "/screens/login/email" },
2193
+ "password": { "$state": "/screens/login/password" }
2194
+ }
2195
+ }
2196
+ }
2197
+ }
2198
+ }
2199
+ }
2200
+ ```
2201
+
2202
+ **AppSpec screen definition for login:**
2203
+ ```json
2204
+ "login": { "label": "Login", "icon": "sign-in", "statePolicy": "reset" }
2205
+ ```
2206
+
2207
+ The `statePolicy: "reset"` is what clears `/screens/login` on every navigation to the login screen. Without it, credentials persist after logout.
2208
+
2209
+ ### Security Guarantees
2210
+
2211
+ - Tokens NEVER exist in the state store — only in engine closure
2212
+ - `$auth` blocks token/password fields via whitelist
2213
+ - Auth headers only injected for `authDomains` URLs
2214
+ - Credentials cleared from state after login (success AND failure)
2215
+ - Refresh mutex: max 1 concurrent refresh (anti-stampede)
2216
+ - Login rate limit: 5 attempts/min with exponential backoff
2217
+ - Cross-tab sync: logout in one tab = logout in all tabs
2218
+ - StateGuard blocks spec writes to `/auth/*`
2219
+ - XSS-resistant by architecture: JSON → primitives → React escaping
2220
+
2221
+ ### Fetch Interceptors
2222
+
2223
+ The framework uses a scoped fetch wrapper (NOT `globalThis.fetch`). All `fetch` and `submitForm` spec actions go through this wrapper. Interceptors can be enabled declaratively in AppSpec:
2224
+
2225
+ ```json
2226
+ {
2227
+ "navigation": {
2228
+ "auth": {
2229
+ "interceptors": {
2230
+ "logging": true,
2231
+ "timeout": { "ms": 15000 },
2232
+ "retryOnError": { "maxRetries": 2, "statuses": [502, 503] }
2233
+ }
2234
+ }
2235
+ }
2236
+ }
2237
+ ```
2238
+
2239
+ | Interceptor | Config | Behavior |
2240
+ |-------------|--------|----------|
2241
+ | **Auth** | Auto when auth configured | Injects Bearer token for `authDomains` URLs. Triggers refresh+retry on 401. |
2242
+ | **Logging** | `"logging": true` | Logs request method/URL and response status. Redacts sensitive query params (token, password, key). |
2243
+ | **Timeout** | `"timeout": { "ms": 15000 }` | Aborts request after configured milliseconds. Uses AbortController. |
2244
+ | **Retry** | `"retryOnError": { ... }` | Retries on transient server errors (502, 503, 504) with exponential backoff. |
2245
+
2246
+ Auth interceptor is always first in the chain. Custom interceptors from the host app run after built-in ones.
2247
+
2248
+ ### Action Middleware
2249
+
2250
+ Pre/post/onError hooks that run on EVERY dispatcher action (not just fetch). Configured at the host app level:
2251
+
2252
+ ```tsx
2253
+ <MythikApp
2254
+ auth={{
2255
+ provider: createSupabaseAuthProvider(client),
2256
+ middleware: [myLoggingMiddleware, myAnalyticsMiddleware],
2257
+ }}
2258
+ />
2259
+ ```
2260
+
2261
+ Middleware API:
2262
+
2263
+ ```typescript
2264
+ {
2265
+ name: "my-middleware",
2266
+ before: (ctx) => {
2267
+ // Runs before action. ctx.action, ctx.params, ctx.getState(), ctx.setParam()
2268
+ },
2269
+ after: (ctx, result) => {
2270
+ // Runs after successful action
2271
+ },
2272
+ onError: (ctx, error) => {
2273
+ // Runs on action failure. Individual errors are isolated.
2274
+ }
2275
+ }
2276
+ ```
2277
+
2278
+ Middleware executes in registration order. `before` can modify params via `ctx.setParam()`. `onError` handlers are isolated — one failing handler does not prevent others from running.
2279
+
2280
+ **Separation of concerns:** Fetch Interceptors = networking (headers, retry, timeout). Action Middleware = business logic (logging, analytics, auth lifecycle).
2281
+
2282
+ ### Session Persistence
2283
+
2284
+ Auth session persistence is configurable in AppSpec:
2285
+
2286
+ | Mode | Config Value | Behavior |
2287
+ |------|-------------|----------|
2288
+ | **Local** | `"persistence": "local"` (default) | Survives browser close. Refresh token + safe user data in localStorage. |
2289
+ | **Session** | `"persistence": "session"` | Lost on browser/tab close. Uses sessionStorage. |
2290
+ | **Memory** | `"persistence": "memory"` | Lost on page refresh. Most secure — re-login required every time. |
2291
+
2292
+ **What's persisted:** Refresh token + user profile (id, email, name, avatar, role, roles, metadata).
2293
+ **What's NEVER persisted:** Access tokens. They live exclusively in the engine closure.
2294
+
2295
+ On page load, `mount()` restores the session from persistence and attempts a token refresh to validate the session is still alive. If refresh fails, the persisted session is cleared.
2296
+
2297
+ ### Cross-Tab Synchronization
2298
+
2299
+ Auth events are automatically synchronized across browser tabs:
2300
+
2301
+ | Event | Trigger | Effect on other tabs |
2302
+ |-------|---------|---------------------|
2303
+ | `SIGNED_IN` | Login completes | Other tabs can restore session from persistence |
2304
+ | `SIGNED_OUT` | Logout | All tabs clear session and redirect to loginScreen |
2305
+ | `TOKEN_REFRESHED` | Token refresh completes | Other tabs pick up new refresh token from persistence |
2306
+ | `SESSION_EXPIRED` | Refresh fails | All tabs clear session |
2307
+
2308
+ **Primary mechanism:** BroadcastChannel API (97%+ browser support).
2309
+ **Fallback:** localStorage `storage` events for legacy browsers.
2310
+
2311
+ No configuration needed — cross-tab sync is automatic when auth is configured.
2312
+
2313
+ ---
2314
+
2315
+ ## Audit (SP3)
2316
+
2317
+ ### Audit Config
2318
+
2319
+ Declarative audit config per endpoint — auto-injects username and timestamp into CRUD INSERT and UPDATE queries:
2320
+
2321
+ ```json
2322
+ {
2323
+ "endpoints": {
2324
+ "items": {
2325
+ "path": "/api/items",
2326
+ "crud": { "table": "Items", "primaryKey": "id", "insertable": ["name"], "updatable": ["name"] },
2327
+ "audit": {
2328
+ "createdBy": "created_by",
2329
+ "createdAt": "created_at",
2330
+ "updatedBy": "updated_by",
2331
+ "updatedAt": "updated_at",
2332
+ "timezone": "UTC"
2333
+ }
2334
+ }
2335
+ }
2336
+ }
2337
+ ```
2338
+
2339
+ All fields are optional — only configured fields are injected.
2340
+
2341
+ ### Behavior
2342
+
2343
+ | Operation | Fields injected | Source |
2344
+ |-----------|----------------|--------|
2345
+ | **INSERT** | `createdBy` + `createdAt` + `updatedBy` + `updatedAt` | `req.user.username` + timestamp |
2346
+ | **UPDATE** | `updatedBy` + `updatedAt` | `req.user.username` + timestamp |
2347
+ | **DELETE** | None | No change |
2348
+
2349
+ ### Timezone
2350
+
2351
+ - Without `timezone` → UTC (default)
2352
+ - With `timezone` → IANA string (e.g., `"America/El_Salvador"`, `"America/Tegucigalpa"`, `"US/Eastern"`)
2353
+ - Use timezone when the database stores local time (most enterprise DBs)
2354
+ - Uses `Intl.DateTimeFormat` — works regardless of server physical location
2355
+
2356
+ ### Security
2357
+
2358
+ - Audit values **override** any client-sent values — prevents spoofing
2359
+ - No username available (public endpoint) → only timestamp fields injected
2360
+
2361
+ ---
2362
+
2363
+ ## Typed Contract (SP4)
2364
+
2365
+ Cross-validate screen specs against api-specs before deploy. Only possible because both sides are declarative JSON.
2366
+
2367
+ ### Usage
2368
+
2369
+ ```bash
2370
+ # Single api-spec
2371
+ mythik contract --app app-demo --api records-api
2372
+
2373
+ # Multiple api-specs (modular backends)
2374
+ mythik contract --app app-demo --api inventory-api,users-api
2375
+
2376
+ # Api-specs in separate table
2377
+ mythik contract --app app-demo --api records-api --api-table api_specs
2378
+
2379
+ # Strip base URL from screen fetch URLs
2380
+ mythik contract --app app-demo --api records-api --base-url http://localhost:3010
2381
+
2382
+ # JSON output for CI/CD (exit code 1 on errors)
2383
+ mythik contract --app app-demo --api records-api --json
2384
+ ```
2385
+
2386
+ ### Rules
2387
+
2388
+ | Rule | Level | What it checks |
2389
+ |------|-------|----------------|
2390
+ | `endpoints-exist` | error | Every fetch URL in screens matches an endpoint, catalog, or builtin in the api-spec |
2391
+ | `fields-valid` | error | POST/PUT body fields exist in CRUD `insertable`/`updatable` definitions |
2392
+ | `params-match` | warning | Query params match endpoint `params` config. `page`/`pageSize` always valid |
2393
+ | `permissions-consistent` | warning | If AppSpec `roleAccess` grants role access to a screen, endpoints used by that screen allow that role via `policies` |
2394
+
2395
+ All rules include Levenshtein "did you mean?" suggestions when names are close matches.
2396
+
2397
+ Duplicate findings (same rule + screen + level + message) are automatically collapsed with a `count` field. Template domain URLs (`http://${authDomain}/api/items`) are normalized to relative paths before matching.
2398
+
2399
+ ### Extensibility
2400
+
2401
+ Rules are pluggable. Adding a new rule = creating one file that implements `ContractRule` interface + adding to the rules array. No engine changes needed. When the backend evolves (data models, enums, response schemas), new rules plug in trivially.
2402
+
2403
+ ---
2404
+
2405
+ ## File Upload
2406
+
2407
+ ### File Upload Primitive
2408
+
2409
+ The `file-upload` primitive is a god-primitive with preview, progress, validation, drop zone, and auto/manual upload modes.
2410
+
2411
+ **Props:**
2412
+
2413
+ | Prop | Type | Default | Description |
2414
+ |------|------|---------|-------------|
2415
+ | `accept` | string | `"*"` | Accepted MIME types or extensions. e.g., `"image/*"`, `".pdf,.docx"`, `"image/jpeg"` |
2416
+ | `multiple` | boolean | `false` | Allow multiple files |
2417
+ | `maxSize` | number | `10485760` (10MB) | Max bytes per file |
2418
+ | `maxFiles` | number | `10` | Max files (when multiple) |
2419
+ | `preview` | boolean | `true` | Show file previews (thumbnails for images, extension icons for others) |
2420
+ | `dropZone` | boolean | `false` | Drag-and-drop area instead of button |
2421
+ | `autoUpload` | boolean | `true` | Upload immediately on file selection |
2422
+ | `label` | string/expression | `"Choose file"` | Button/zone text. Supports `$i18n` |
2423
+
2424
+ **Example — avatar upload:**
2425
+ ```json
2426
+ {
2427
+ "type": "file-upload",
2428
+ "props": {
2429
+ "accept": "image/*",
2430
+ "maxSize": 5242880,
2431
+ "dropZone": true,
2432
+ "label": { "$i18n": "upload.avatar" }
2433
+ },
2434
+ "on": {
2435
+ "upload": {
2436
+ "action": "uploadFile",
2437
+ "params": {
2438
+ "bucket": "avatars",
2439
+ "path": { "$template": "users/${/auth/user/id}/${filename}" },
2440
+ "target": "/form/avatarUrl"
2441
+ }
2442
+ }
2443
+ }
2444
+ }
2445
+ ```
2446
+
2447
+ **Example — document upload (manual):**
2448
+ ```json
2449
+ {
2450
+ "type": "file-upload",
2451
+ "props": {
2452
+ "accept": ".pdf,.docx,.xlsx",
2453
+ "multiple": true,
2454
+ "maxFiles": 5,
2455
+ "autoUpload": false,
2456
+ "label": "Attach documents"
2457
+ },
2458
+ "on": {
2459
+ "upload": {
2460
+ "action": "uploadFile",
2461
+ "params": {
2462
+ "bucket": "documents",
2463
+ "target": "/form/attachments"
2464
+ }
2465
+ }
2466
+ }
2467
+ }
2468
+ ```
2469
+ With `autoUpload: false`, files are selected and previewed but not uploaded until triggered by a button with the `uploadFile` action.
2470
+
2471
+ ### `uploadFile` Action
2472
+
2473
+ Uploads files to storage via the configured StorageAdapter. Writes the resulting URL(s) to the `target` state path.
2474
+
2475
+ | Param | Type | Required | Description |
2476
+ |-------|------|----------|-------------|
2477
+ | `bucket` | string | Yes | Storage bucket name |
2478
+ | `path` | string/expression | No | Path within bucket. `${filename}` replaced with original name. Default: original filename |
2479
+ | `target` | string | Yes | State path to write URL(s). String for single file, array for multiple |
2480
+
2481
+ **Behavior:**
2482
+ - Validates file type against `accept` and `maxSize`
2483
+ - Uploads via StorageAdapter with progress tracking
2484
+ - Writes progress to `/ui/uploads/{elementId}/files` (internal — don't read directly)
2485
+ - On success: URL written to `target`, status `done`
2486
+ - On failure: 1 automatic retry after 1s, then `error` status with retry button
2487
+ - Single file → `target` receives a URL string
2488
+ - Multiple files → `target` receives a URL array
2489
+
2490
+ ### `deleteFile` Action
2491
+
2492
+ Deletes a file from storage.
2493
+
2494
+ | Param | Type | Required | Description |
2495
+ |-------|------|----------|-------------|
2496
+ | `path` | string | Yes | File path in storage |
2497
+ | `bucket` | string | Yes | Storage bucket name |
2498
+
2499
+ ```json
2500
+ { "action": "deleteFile", "params": { "path": { "$state": "/form/avatarPath" }, "bucket": "avatars" } }
2501
+ ```
2502
+
2503
+ ### StorageAdapter Setup (Host App)
2504
+
2505
+ The host app provides a StorageAdapter to enable file uploads:
2506
+
2507
+ **Supabase Storage:**
2508
+ ```tsx
2509
+ import { createSupabaseStorageAdapter } from 'mythik';
2510
+
2511
+ <MythikRenderer
2512
+ spec={spec}
2513
+ instance={svc}
2514
+ storage={createSupabaseStorageAdapter({ client: supabaseClient })}
2515
+ />
2516
+ ```
2517
+
2518
+ **Generic URL (any backend):**
2519
+ ```tsx
2520
+ import { createUrlStorageAdapter } from 'mythik';
2521
+
2522
+ <MythikRenderer
2523
+ spec={spec}
2524
+ instance={svc}
2525
+ storage={createUrlStorageAdapter({
2526
+ uploadUrl: 'https://api.example.com/upload',
2527
+ deleteUrl: 'https://api.example.com/files',
2528
+ responsePath: 'data.url',
2529
+ headers: { 'X-Api-Key': apiKey },
2530
+ })}
2531
+ />
2532
+ ```
2533
+
2534
+ **Global limits (optional security backstop):**
2535
+ ```tsx
2536
+ <MythikRenderer
2537
+ spec={spec}
2538
+ instance={svc}
2539
+ storage={adapter}
2540
+ storageConfig={{
2541
+ allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
2542
+ maxSize: 10_485_760,
2543
+ }}
2544
+ />
2545
+ ```
2546
+
2547
+ ### Upload State Paths
2548
+
2549
+ | Path | Owner | Description |
2550
+ |------|-------|-------------|
2551
+ | `target` (e.g., `/form/avatarUrl`) | Spec author | URL string (single) or URL array (multiple). Use this for persistence |
2552
+ | `/ui/uploads/{elementId}/files` | Framework | Array of `{ name, size, type, progress, status, previewUrl, error }`. Internal — don't read directly |
2553
+
2554
+ The primitive reads `/ui/uploads/*` automatically. The spec author only needs to read the `target` path.
2555
+
2556
+ ---
2557
+
2558
+ ## Skeleton Primitive
2559
+
2560
+ Loading placeholders that replicate spec structure while data loads.
2561
+
2562
+ ### Auto-Skeleton (Zero Config)
2563
+
2564
+ When a spec has `initialActions` with a `fetch` action, the renderer auto-detects loading state and replaces data-dependent primitives with shimmer skeletons. No configuration needed.
2565
+
2566
+ **Activation conditions (all must be true):**
2567
+ 1. Spec has `initialActions` containing a `fetch` action
2568
+ 2. `/ui/loading` is `true`
2569
+ 3. The fetch target path is empty/undefined/empty array
2570
+
2571
+ **Shape mapping:** `text` → text line, `image` → rectangle, `button` → button shape, `input/textarea/select` → input rectangle, `icon` → circle, charts → large rectangle, `table` → table placeholder. Layout containers (`stack`, `grid`, `box`, `scroll`) pass through to children. Overlays (`modal`, `drawer`, `tabs`) are skipped.
2572
+
2573
+ ### Element-Level Opt-Out
2574
+
2575
+ ```json
2576
+ {
2577
+ "type": "text",
2578
+ "props": { "content": "Static Header" },
2579
+ "skeleton": false
2580
+ }
2581
+ ```
2582
+
2583
+ Elements with `skeleton: false` render normally during loading. Use for static content that doesn't depend on fetched data.
2584
+
2585
+ ### Manual Skeleton
2586
+
2587
+ For custom loading layouts, place `type: "skeleton"` elements directly:
2588
+
2589
+ ```json
2590
+ {
2591
+ "type": "skeleton",
2592
+ "props": {
2593
+ "variant": "text",
2594
+ "width": "80%",
2595
+ "height": 16,
2596
+ "count": 3,
2597
+ "gap": 8
2598
+ },
2599
+ "visible": { "$state": "/ui/loading" }
2600
+ }
2601
+ ```
2602
+
2603
+ | Prop | Type | Default | Description |
2604
+ |------|------|---------|-------------|
2605
+ | `variant` | `"text" \| "circle" \| "rect"` | `"rect"` | Shape of the skeleton |
2606
+ | `width` | `string \| number` | `"100%"` | Width (px or %) |
2607
+ | `height` | `number` | `16` | Height in px |
2608
+ | `count` | `number` | `1` | Number of shapes to render |
2609
+ | `gap` | `number` | `8` | Gap between shapes (when count > 1) |
2610
+
2611
+ ### Disable Auto-Skeleton
2612
+
2613
+ Set `autoSkeleton={false}` on `MythikRenderer` to disable completely:
2614
+
2615
+ ```tsx
2616
+ <MythikRenderer spec={spec} instance={svc} autoSkeleton={false} />
2617
+ ```
2618
+
2619
+ ---
2620
+
2621
+ ## Export Action
2622
+
2623
+ Declarative data export with CSV built-in and adapter pattern for XLSX/PDF.
2624
+
2625
+ ### Usage
2626
+
2627
+ ```json
2628
+ {
2629
+ "action": "export",
2630
+ "params": {
2631
+ "source": "/tasks",
2632
+ "columns": [
2633
+ { "field": "title", "label": "Title" },
2634
+ { "field": "price", "label": "Price", "format": "currency", "formatOptions": { "currency": "USD" } },
2635
+ { "field": "createdAt", "label": "Created", "format": "date" }
2636
+ ],
2637
+ "filename": "tasks-report",
2638
+ "format": "csv"
2639
+ }
2640
+ }
2641
+ ```
2642
+
2643
+ | Param | Type | Required | Description |
2644
+ |-------|------|----------|-------------|
2645
+ | `source` | string | Yes | State path to array of data |
2646
+ | `columns` | ExportColumn[] | Yes | Column definitions with optional formatting |
2647
+ | `filename` | string | Yes | Download filename (without extension) |
2648
+ | `format` | string | No | `"csv"` (default), or any custom format with registered adapter |
2649
+
2650
+ ### Column Formatting
2651
+
2652
+ | Format | Example Output | Options |
2653
+ |--------|---------------|---------|
2654
+ | `currency` | `$1,234.50` | `currency` (code), `decimals`, `locale` |
2655
+ | `number` | `1,234.50` | `decimals`, `locale` |
2656
+ | `percent` | `75%` | `decimals`, `locale` |
2657
+ | `date` | `4/5/2026` | `locale` |
2658
+
2659
+ ### CSV (Built-in, Zero Dependencies)
2660
+
2661
+ Format `"csv"` is always available. RFC 4180 escaping. Downloads as `.csv` file.
2662
+
2663
+ ### XLSX / PDF (Via Adapter)
2664
+
2665
+ Register adapters via `exportAdapters` prop:
2666
+
2667
+ ```tsx
2668
+ <MythikRenderer
2669
+ spec={spec}
2670
+ instance={svc}
2671
+ exportAdapters={{
2672
+ xlsx: myXlsxAdapter,
2673
+ pdf: myPdfAdapter,
2674
+ }}
2675
+ />
2676
+ ```
2677
+
2678
+ Adapters implement `ExportAdapter`:
2679
+
2680
+ ```typescript
2681
+ interface ExportAdapter {
2682
+ generate(data: ExportData, format: string): Promise<Blob>;
2683
+ }
2684
+ ```
2685
+
2686
+ `ExportData` includes both raw `rows` and pre-formatted `formattedRows`. XLSX adapters may use raw values with number formatting, PDF adapters may use formatted strings.
2687
+
2688
+ ---
2689
+
2690
+ ## Deep Design Token System
2691
+
2692
+ Three-layer token resolution that gives every app a unique visual identity from minimal configuration:
2693
+
2694
+ ```
2695
+ Layer 1: Framework DEFAULTS (always present — app looks good out of the box)
2696
+ Layer 2: DNA derivation (OKLCH color science — 8 seeds generate full identity)
2697
+ Layer 3: Manual overrides (explicit values always win)
2698
+ ```
2699
+
2700
+ ### DNA Seeds
2701
+
2702
+ Define in `tokens.dna` at the AppSpec level. The framework derives ALL token categories automatically:
2703
+
2704
+ ```json
2705
+ {
2706
+ "tokens": {
2707
+ "dna": {
2708
+ "primary": "#0D9488",
2709
+ "harmony": "analogous",
2710
+ "roundness": 0.7,
2711
+ "density": 0.5,
2712
+ "depth": 0.6,
2713
+ "motion": "fluid",
2714
+ "formality": 0.3
2715
+ }
2716
+ }
2717
+ }
2718
+ ```
2719
+
2720
+ | Seed | Type | Default | Controls |
2721
+ |------|------|---------|----------|
2722
+ | `primary` | hex | (required) | Base color → OKLCH 13-stop tonal palette |
2723
+ | `harmony` | `complementary` \| `analogous` \| `triadic` \| `split-complementary` | `complementary` | Accent color hue rotation (analogous=+50°, triadic=+120°, split=+150°, complementary=+180°) |
2724
+ | `accent` | hex | derived | Explicit accent (skips harmony) |
2725
+ | `neutral` | `warm` \| `cool` \| `natural` | `natural` | Neutral palette tint. warm=amber hue (55°), cool=blue (250°), natural=primary hue. Chroma 0.025 (natural) / 0.035 (warm/cool) — visible in backgrounds and borders |
2726
+ | `roundness` | 0–1 | 0.5 | Border radii: 0=sharp, 1=pill |
2727
+ | `density` | 0–1 | 0.5 | Font size + spacing: 0=airy, 1=compact |
2728
+ | `depth` | 0–1 | 0.5 | Shadow intensity: 0=flat, 1=elevated |
2729
+ | `motion` | `fluid` \| `snappy` \| `gentle` \| `energetic` | `gentle` | Animation duration, spring, stagger |
2730
+ | `formality` | 0–1 | 0.5 | Typography: 5 font tiers. Body: Inter → Space Grotesk → Source Sans 3 → Lora → Merriweather. Heading: Inter → Space Grotesk → DM Sans → Lora → Playfair Display. Also drives continuous letterSpacing (0 → 0.03em), headingLetterSpacing (0 → -0.02em), lineHeight ratio (1.45 → 1.6), bold weight (700 → 800) |
2731
+
2732
+ ### Three Control Levels
2733
+
2734
+ **Level 1 — Just a color:** `"dna": { "primary": "#0D9488" }` → full coherent identity.
2735
+
2736
+ **Level 2 — Personality:** `"dna": { "primary": "#0D9488", "roundness": 0.7, "motion": "fluid" }` → unique identity from 5 values.
2737
+
2738
+ **Level 3 — Override specific tokens:** DNA generates base, explicit values win:
2739
+ ```json
2740
+ { "tokens": { "dna": { "primary": "#0D9488" }, "colors": { "error": "#DC2626" }, "shape": { "radius": { "md": 24 } } } }
2741
+ ```
2742
+
2743
+ ### Token Categories & `$token` Paths
2744
+
2745
+ All primitives consume these via `useDesignTokens` hook internally. Specs reference them via `$token`:
2746
+
2747
+ | Category | Paths | Example |
2748
+ |----------|-------|---------|
2749
+ | Colors | `colors.primary`, `colors.surface`, `colors.text`, `colors.error`, `colors.accent`, etc. (13 semantic colors) | `{ "$token": "colors.primary" }` |
2750
+ | Shape | `shape.radius.none/sm/md/lg/xl/full` | `{ "$token": "shape.radius.lg" }` |
2751
+ | Typography | `typography.fontFamily.base/heading/mono`, `typography.scale.xs/sm/md/lg/xl/2xl` (each has `.fontSize`, `.lineHeight`), `typography.weight.normal/medium/semibold/bold`, `typography.letterSpacing`, `typography.headingLetterSpacing` | `{ "$token": "typography.scale.xl.fontSize" }` |
2752
+ | Spacing | `spacing.unit`, `spacing.scale.xs/sm/md/lg/xl/2xl` | `{ "$token": "spacing.scale.md" }` |
2753
+ | Elevation | `elevation.none/sm/md/lg/xl` — auto-converts to CSS `boxShadow` string when used in `$token` | `{ "$token": "elevation.lg" }` |
2754
+ | Motion | `motion.duration.fast/normal/slow`, `motion.easing.default/enter/exit`, `motion.spring.damping/stiffness/mass`, `motion.stagger` | `{ "$token": "motion.duration.fast" }` |
2755
+ | Opacity | `opacity.disabled/pressed/backdrop/muted` | `{ "$token": "opacity.backdrop" }` |
2756
+
2757
+ ### Auto Dark Mode
2758
+
2759
+ DNA automatically generates `modes.dark` by inverting tonal palette stops. No manual dark theme needed. Override with explicit `modes.dark` values if desired. `toggleTheme` action works automatically.
2760
+
2761
+ ### Runtime Token Update
2762
+
2763
+ Use `updateTokens` action to change tokens without page reload:
2764
+ ```json
2765
+ { "action": "updateTokens", "params": { "dna": { "primary": "#F97316", "roundness": 0.9 } } }
2766
+ ```
2767
+ All primitives re-render with new tokens. Numeric seeds > 1 are auto-divided by 100 (slider 0-100 compatibility).
2768
+
2769
+ ### Presets System
2770
+
2771
+ Complete DNA+Identity snapshots that transform the entire look of an app with a single action. The framework provides the **tools** (contract, registration, application); preset definitions live **outside core** (in the app, a file, or a future API like mythik.dev).
2772
+
2773
+ **PresetDefinition interface:**
2774
+ ```typescript
2775
+ interface PresetDefinition {
2776
+ id: string; // 'startup-saas'
2777
+ name: string; // 'Startup SaaS'
2778
+ description: string; // 'Clean, professional, outlined'
2779
+ tags?: string[]; // ['professional', 'minimal']
2780
+ tokens: {
2781
+ dna: DnaSeed;
2782
+ identity: IdentityConfig;
2783
+ };
2784
+ }
2785
+ ```
2786
+
2787
+ **Registration:**
2788
+ ```typescript
2789
+ const svc = createMythik({ tokens: { dna: { primary: '#0D9488' } } });
2790
+ svc.plugins.registerPresets(myPresets); // stores presets + writes /presets/available to state
2791
+ svc.applyPlugins();
2792
+ ```
2793
+
2794
+ `registerPresets` automatically:
2795
+ 1. Stores presets in internal Map
2796
+ 2. Writes dropdown options to state at `/presets/available` (array of `{ value, label }`)
2797
+ 3. Auto-registers `applyPreset` action
2798
+
2799
+ **Applying a preset from a spec:**
2800
+ ```json
2801
+ { "action": "applyPreset", "params": { "preset": "startup-saas" } }
2802
+ ```
2803
+
2804
+ Uses `_replace: true` internally — fully replaces all tokens, no stale values from previous presets.
2805
+
2806
+ `applyPreset` with `preset: 'custom'` is a no-op (silently returns). Unknown preset IDs throw.
2807
+
2808
+ **Dynamic dropdown in specs:**
2809
+ ```json
2810
+ {
2811
+ "type": "select",
2812
+ "props": {
2813
+ "options": { "$state": "/presets/available" },
2814
+ "value": { "$bindState": "/ui/currentPreset" }
2815
+ },
2816
+ "on": {
2817
+ "change": [{ "action": "applyPreset", "params": { "preset": { "$state": "/ui/currentPreset" } } }]
2818
+ }
2819
+ }
2820
+ ```
2821
+
2822
+ **Accessors:** `plugins.getPresets()` returns all registered presets. `plugins.getPreset(id)` returns one by ID.
2823
+
2824
+ ### Font Loading
2825
+
2826
+ DNA generates font family names but does NOT load fonts. Web projects must include `@font-face` or Google Fonts. RN projects use `expo-font`. This is the project's responsibility, not the framework's.
2827
+
2828
+ ### CLI Token Inspection
2829
+
2830
+ ```bash
2831
+ mythik tokens --dna '{"primary":"#0D9488","roundness":0.7}' --json
2832
+ ```
2833
+ Shows the full resolved token set for debugging and inspection.
2834
+
2835
+ ---
2836
+
2837
+ ## Component Variants
2838
+
2839
+ Token-driven component styling for any primitive. Define variant definitions in `tokens.components` — the engine applies style, hover, active, focus, and transition automatically.
2840
+
2841
+ ### Defining Variants
2842
+
2843
+ Variants live in `tokens.components.{primitiveType}.{variantName}`:
2844
+
2845
+ ```json
2846
+ {
2847
+ "tokens": {
2848
+ "colors": { "primary": "#0D9488", "surface": "#FFFFFF", "text": "#0F172A", "error": "#EF4444" },
2849
+ "radius": { "md": 12, "lg": 16 },
2850
+ "shadow": { "md": "0 4px 6px rgba(0,0,0,0.07)", "lg": "0 10px 25px rgba(0,0,0,0.1)" },
2851
+
2852
+ "components": {
2853
+ "button": {
2854
+ "primary": {
2855
+ "style": { "backgroundColor": "$colors.primary", "color": "#FFF", "borderRadius": "$radius.md", "padding": "10px 22px", "fontWeight": 700 },
2856
+ "hover": { "scale": 1.05, "y": -1 },
2857
+ "active": { "scale": 0.95 },
2858
+ "transition": { "duration": 0.15 }
2859
+ },
2860
+ "danger": {
2861
+ "style": { "backgroundColor": "$colors.error", "color": "#FFF", "borderRadius": "$radius.md" },
2862
+ "hover": { "scale": 1.03 },
2863
+ "active": { "scale": 0.95 }
2864
+ }
2865
+ },
2866
+ "box": {
2867
+ "card": {
2868
+ "style": { "backgroundColor": "$colors.surface", "borderRadius": "$radius.lg", "padding": 16, "boxShadow": "$shadow.md" },
2869
+ "hover": { "boxShadow": "$shadow.lg", "y": -2 },
2870
+ "transition": { "duration": 0.2 }
2871
+ }
2872
+ },
2873
+ "text": {
2874
+ "heading": {
2875
+ "style": { "fontSize": 24, "fontWeight": 700, "color": "$colors.text" }
2876
+ }
2877
+ }
2878
+ }
2879
+ }
2880
+ }
2881
+ ```
2882
+
2883
+ ### Using Variants in Specs
2884
+
2885
+ Just set `variant` in props — no style copying needed:
2886
+
2887
+ ```json
2888
+ { "type": "button", "props": { "label": "Save", "variant": "primary" } }
2889
+ { "type": "button", "props": { "label": "Delete", "variant": "danger" } }
2890
+ { "type": "box", "props": { "variant": "card" }, "children": ["content"] }
2891
+ ```
2892
+
2893
+ ### Supported Variant Properties
2894
+
2895
+ | Property | Type | Purpose |
2896
+ |----------|------|---------|
2897
+ | `style` | object | CSS styles — applied as base |
2898
+ | `hover` | object | Hover state overrides |
2899
+ | `active` | object | Active/tap state overrides |
2900
+ | `focus` | object | Focus state overrides |
2901
+ | `transition` | object | Animation config |
2902
+
2903
+ ### `$path` Token References
2904
+
2905
+ String values starting with `$` reference other tokens. Resolved against the active token tree (with dark mode already applied):
2906
+
2907
+ - `"$colors.primary"` → `"#0D9488"`
2908
+ - `"$shape.radius.md"` → `12`
2909
+ - `"$elevation.lg"` → CSS boxShadow string
2910
+ - `"$typography.weight.bold"` → `700`
2911
+ - `"$spacing.scale.md"` → `16`
2912
+
2913
+ Legacy paths (`$radius.md`, `$shadow.lg`) still work for backward compatibility.
2914
+
2915
+ **Dark mode works automatically.** When dark mode changes `colors.surface` to `#1E293B`, any variant using `$colors.surface` gets the dark value — no separate dark variant needed.
2916
+
2917
+ ### Merge Precedence
2918
+
2919
+ Variant is base, element-level overrides. Same as CSS (class defaults + inline override):
2920
+
2921
+ ```json
2922
+ {
2923
+ "type": "button",
2924
+ "props": { "variant": "primary" },
2925
+ "style": { "borderRadius": 20 },
2926
+ "hover": { "rotate": 5 }
2927
+ }
2928
+ ```
2929
+
2930
+ Result: variant's `backgroundColor`, `color`, `padding` apply. Element's `borderRadius: 20` overrides variant's `borderRadius: 12`. Hover merges: variant's `scale: 1.05` + element's `rotate: 5`.
2931
+
2932
+ ### Works for Any Primitive
2933
+
2934
+ Not just buttons — boxes, text, inputs, any primitive type can have variants defined in `tokens.components`.
2935
+
2936
+ ---
2937
+
2938
+ ## Background System v2 — Layered Backgrounds
2939
+
2940
+ Mythik backgrounds use a structured `LayerBackground` model. Apps declare `tokens.identity.background` with a base color and ordered layers; the renderer mounts a root `BackgroundStack` so backgrounds remain app-level composition, not per-card decoration.
2941
+
2942
+ ### Layered background contract
2943
+
2944
+ 171. **`LayerBackground` type** — New structured background type with `color?: string` and `layers?: LayerConfig[]`. Unions: `SolidLayerConfig`, `GradientLayerConfig`, `PatternLayerConfig`, `GrainLayerConfig`, `ImageLayerConfig`. See `packages/core/src/design/identity/types.ts`
2945
+
2946
+ 172. **`resolveBackgroundLayers(config)` function** — Transforms `LayerBackground` into `LayerSpec[]` (structured renderer-ready specs). Base color always emitted first, then layers in array order. `zIndex` uses array index as fallback or explicit override. Entry point for any consumer rendering custom backgrounds
2947
+
2948
+ 173. **8 curated background recipes shipped in `tokens.backgrounds.*`** — `linear-aura` (Linear-like), `stripe-ribbons` (Stripe-like), `vercel-center`, `arc-organic`, `grid-subtle`, `notion-warm`, `raycast-mono`, `comic-pop`. Exported as `BACKGROUND_RECIPES` constant. Consumers access via recipe name string or expand and modify
2949
+
2950
+ 174. **Pattern primitives** — 6 curated kinds (`grid`, `dots`, `diagonal`, `iso`, `crosshatch`, `chevron`) + `custom-svg` escape hatch. Each emits `<pattern>` SVG fragment. Params: `spacing`, `thickness`, `color`, `angle`, `dotRadius` (dots only). Custom-svg passes through `sanitizeSVGShapes` (strict allowlist: shapes + geometric attrs only; rejects `<script>`, `<foreignObject>`, `<use href>`, `on*` events, `javascript:` URLs)
2951
+
2952
+ 175. **Gradient primitive enhanced** — Linear/radial/conic with custom `stops[]` (each with `color`, `opacity`, `at` percentage), `position`, `size`, `shape`. Opacity→rgba conversion automatic. Previously was `{ angle }` only with hardcoded `30% 50%` position and fixed opacity values. New API: `{ type: 'gradient', kind: 'radial', shape: 'circle', size: '500px', position: '0% 20%', stops: [...] }`
2953
+
2954
+ 176. **Grain primitive** — `{ type: 'grain', intensity, scale, monochrome }` emits SVG `feTurbulence` filter for noise overlay. Default `intensity: 0.05`, `scale: 0.9`, `monochrome: true` (premium desaturated grain). Cross-platform (web + react-native-svg)
2955
+
2956
+ 177. **Layer composition via `layers[]` array** — Any mix of primitives (solid, gradient, pattern, grain, image) composed as ordered stack. Each layer supports `opacity`, `blendMode` (normal, multiply, screen, overlay, soft-light, hard-light, color-dodge, color-burn), `zIndex`
2957
+
2958
+ 178. **`BackgroundLayer` + `BackgroundStack` components shipped for web and RN** — Web: `packages/react/src/background/` using CSS for solid/gradient/image + inline SVG for pattern/grain. RN: `packages/react-native/src/background/` using `react-native-svg` `SvgXml` for all layers + View for solid + Image for image. Accepts `LayerBackground` config OR recipe string name
2959
+
2960
+ 179. **`custom-svg` sanitized escape hatch** — When built-in patterns aren't enough, consumers pass SVG shape fragments via `{ kind: 'custom-svg', shapes: '<circle.../>', tileSize: 20 }`. `sanitizeSVGShapes` uses regex pre-strip + DOMPurify allowlist. Only circle/rect/path/line/polygon/polyline/ellipse/g/defs/gradients + geometric attrs survive. Cross-platform safe (isomorphic-dompurify)
2961
+
2962
+ ---
2963
+
2964
+ ## Animation System
2965
+
2966
+ `Element.animations` is a first-class spec field. Animations can be declared directly on elements and can also cascade through identity, component variants, templates, and custom elements. The same core animation contract feeds web CSS keyframes and React Native Reanimated output where the platform supports the property.
2967
+
2968
+ ### Animation contract
2969
+
2970
+ 180. **`ElementAnimations` type with 7 triggers** — `mount`, `unmount`, `hover`, `focus`, `active`, `ambient`, `stateChange`. Each trigger accepts `AnimationRef | AnimationRef[] | null`; `null` explicitly disables inherited animation for that trigger in the cascade. See `packages/core/src/design/animation/types.ts`
2971
+
2972
+ 181. **`AnimationRef` union: recipe reference or inline animation** — `{ recipe: string, duration?, easing?, delay?, iterations?, stagger?, essential? }` references a curated recipe with optional overrides. Alternative: `InlineAnimation` with explicit `keyframes: KeyframeSnapshot[]`. Resolver throws if a ref contains both `recipe` AND `keyframes` (malformed)
2973
+
2974
+ 182. **`KeyframeSnapshot` schema** — `{ at: string, opacity?, transform?: TransformValue, backgroundColor?, borderColor?, borderRadius?, borderWidth?, color?, filter?: FilterValue }`. `at` must be percent format `'0%'` to `'100%'` (out-of-range throws); keyframes must be monotonically non-decreasing. `TransformValue` supports translateX/Y, scale (number or `{x,y}`), rotate, skewX/Y
2975
+
2976
+ 183. **`resolveAnimation(ref, recipes)` function** — Transforms `AnimationRef` into fully-normalized `AnimationSpec` with parsed durations (handles `'200ms'`, `'1.5s'`, numeric), defaults applied (`iterations: 1`, `direction: 'normal'`, `fillMode: 'both'`, `easing: 'ease-out'`, `delay: 0`, `essential: false`). Throws on unknown recipe, empty keyframes, non-monotonic `at`, out-of-range `at`
2977
+
2978
+ 184. **15 curated animation recipes shipped in `ANIMATION_RECIPES`** — `fade`, `fade-up`, `fade-down`, `scale-in`, `slide-left`, `slide-right`, `lift`, `glow`, `pulse-primary`, `breathe-subtle`, `shimmer`, `float`, `pop`, `shake`, `spin`. Recipe names are mechanical for AI consistency. Platform limitations are explicit: web-only recipes surface validator warnings on React Native instead of silently pretending to render parity.
2979
+
2980
+ 185. **`buildCSSKeyframes(spec)` function — web output** — Returns `{ name, keyframesText, animationCSS }`. `name` is `svka-${djb2(fingerprint)}` — deterministic hash of content for Constructable StyleSheets dedup. `animationCSS` is CSS shorthand (`name duration easing delay iter direction fillMode`). `keyframesText` derives `at` from normalized fraction (not raw input) for whitespace-clean output
2981
+
2982
+ 186. **`buildReanimatedSpec(spec)` function — RN output** — Returns `{ animatedProps, inputRange, outputRanges, animatedColorProps, timing }`. Emits plain data (no React, no Reanimated imports) — the RN runner wires shared values and `useAnimatedStyle` separately. Nearest-neighbor fill for missing property values at keyframe stops. Numeric rotate/skew stored as `rotateDeg`/`skewXDeg`/`skewYDeg` for correct `interpolate()` input. Non-deg rotate strings (`0.25turn`, `3.14rad`) are skipped on RN (web-only). Percentage `borderRadius` throws on RN (cannot animate natively)
2983
+
2984
+ 187. **`applyReducedMotion(spec, trigger)` — moderate a11y policy** — `mount`/`unmount`/`active` strip transforms (keep opacity; returns null if all animated props disappear — I1 degenerate guard). `hover`/`ambient` return null (skip). `focus` preserved (a11y-critical). `stateChange` reduces duration 3x with floor at 50ms. `essential: true` on the spec bypasses entire policy — intended for spinners/loaders where motion is affordance
2985
+
2986
+ 188. **`validateAnimationCaps(animations)` — performance gates** — Per-trigger: soft-cap 3 animations (warn), hard-cap 6 (error). Per-element: soft-cap 5 triggers with animations (warn), hard-cap 7 (error — all triggers filled). Returns `{ warnings: string[], errors: string[] }`. `null` triggers excluded from count
2987
+
2988
+ 189. **`StateChangeAnimation` schema** — `{ watch: string, on?, recipe?, keyframes?, duration, easing?, debounce? }`. `on` patterns: `'change' | 'increase' | 'decrease' | 'truthy' | 'falsy' | { equals: unknown }`. Must supply either `recipe` or `keyframes` (both missing throws at resolution). Single-fire semantics: for repeating state-driven behavior, use `ambient` with conditional enablement via `$cond`. State-change animations should return to baseline or the transient clear can cause a visible snap.
2989
+
2990
+ 190. **Constructable StyleSheets singleton (web) + `<style>.insertRule` fallback** — `packages/react/src/animation/stylesheet-singleton.ts`. `registerKeyframes(name, text)` idempotent dedup by name. Feature-detected Constructable support with iterability probe (jsdom defines `adoptedStyleSheets` non-iterably — fallback path activates). SSR-safe (`canUseDOM()` guards). Zero `dangerouslySetInnerHTML` in either path — CSSOM only
2991
+
2992
+ 191. **`useElementAnimations` hook (web)** — `packages/react/src/animation/useElementAnimations.ts`. Signature: `(ref, animations, options) => { triggerUnmount }`. Composes active triggers into CSS `animation` shorthand (comma-separated). State flags (`isHovered`/`isFocused`/`isActive`) passed via options — hook does NOT attach DOM listeners. `stateChange` subscribes via `StateStore.subscribePath` with debounce + auto-clear. I1 mid-transient cleanup: effect re-run resets transient state. `triggerUnmount` imperative returns Promise resolving after max duration
2993
+
2994
+ 192. **`useElementAnimations` hook (RN)** — `packages/react-native/src/animation/useElementAnimations.ts`. Signature: `(animations, options) => { animatedStyle, triggerUnmount }`. `composeRNStyle` (pure function in `compose-rn-style.ts`) merges contributions with CSS-parity semantics: scalars last-wins, transforms merge-by-key (not concat — prevents multiplicative composition). Supports arrays per trigger via fixed-pool of 6 `useSharedValue` per trigger (matches validator hard-cap). SVs snap to 0 when trigger transitions to empty to prevent `withRepeat` UI-thread leak. All 7 triggers supported; stateChange via `withSequence(withTiming up/2, withTiming down/2)`
2995
+
2996
+ 193. **`react-native-reanimated` mock for jsdom test env** — `packages/react-native/tests/__mocks__/react-native-reanimated.ts`. End-state-synchronous semantics: `withTiming/withSpring/withDelay/withSequence/withRepeat` return target value immediately; `useAnimatedStyle` evaluates factory synchronously at each render. Covers 30+ Reanimated APIs (shared values, Easing, interpolate, interpolateColor, layout-animation builders FadeIn/FadeOutUp/etc, Animated.View/Text/ScrollView/Image passthrough with style-array flattening + testID/accessibilityRole/Label translation + RN-native event prop filtering). Closed 3 pre-existing RN test failures on integration
2997
+
2998
+ 194. **`Element.animations` field — first-class spec field** — `Element` accepts `animations?: ElementAnimations | null`. Per-trigger `null` disables inherited cascade for that trigger; whole-field `null` disables all element animations. Accepts the 7 triggers `mount`/`unmount`/`hover`/`focus`/`active`/`ambient`/`stateChange`, each taking `AnimationRef | AnimationRef[] | null` (stateChange uses the distinct `StateChangeAnimation` shape)
2999
+
3000
+ 195. **`<Box>` primitive consumes `useElementAnimations` (web)** — Box tracks `isHovered`/`isFocused`/`isActive` via internal state + composed event handlers (state-setter runs BEFORE user handler for throw-safety). Handlers memoized by user-prop identities. When `animations.focus` is set and `tabIndex` is omitted, tabIndex auto-defaults to `0` (prevents silent trigger no-op on non-focusable div); explicit consumer `tabIndex` (including `-1`) always wins. Accepts `animations?: ElementAnimations | null` — null-coalesces to undefined at the hook boundary
3001
+
3002
+ 196. **`<Box>` primitive consumes `useElementAnimations` (RN)** — Root is ALWAYS `Animated.View` (never conditionally swapped with plain View — switching component type between renders would force unmount/remount). Event handlers use RN-native names (`onHoverIn`/`onHoverOut`/`onPressIn`/`onPressOut`/`onFocus`/`onBlur`) composed with internal state setters. Style-merge contract: `animatedStyle` is LAST in the style array so animation properties override user-supplied `style` during animation. `useDesignTokens` is hoisted above the early return to avoid Rules-of-Hooks violation if `surface` toggles.
3003
+
3004
+ 197. **`boxShadow` keyframe field** — `KeyframeSnapshot.boxShadow?: string` accepts a literal CSS shorthand (`'none'` | `'0 0 20px 4px rgba(...)'`). CSS builder emits `box-shadow: ${value}`; RN builder explicitly omits (same policy as `filter` — no 1:1 platform mapping yet). Used by the `glow` recipe: `'none'` → `'0 0 20px 4px rgba(99,102,241,0.45)'` for a colored shadow halo. This models glow as brightness/shadow, not background color wash.
3005
+
3006
+ 198. **`WEB_ONLY_RECIPES` constant + validator warning** — `WEB_ONLY_RECIPES: ReadonlySet<string>` exported from `mythik` (currently `{ 'glow' }`). `validateAnimationCaps` walks each trigger's AnimationRefs and emits a warning when a web-only recipe is referenced, calling out the RN no-op until per-platform shadow parsing lands. Accessible via the `AnimationValidationResult.warnings` array
3007
+
3008
+ 199. **Recipe tuning** — `breathe-subtle` uses scale `1.025` and duration `2800ms` so the motion reads as alive without becoming nervous. `glow` uses a boxShadow halo (see rule 197). Other recipes keep conservative defaults for broad UI safety.
3009
+
3010
+ 200. **Legacy motion system removed** — The legacy `identity.motionEntrance` / `identity.motionHover` path is no longer supported. The animation engine is the canonical path: use `element.animations` and cascade-level animation declarations instead. Also see rule 149 and `ai-context.md` rule 54.
3011
+
3012
+ ### Current platform limitations
3013
+
3014
+ - RN parity for `boxShadow` and `filter` keyframes is intentionally limited because native shadows, Android elevation, and SVG/CSS filters do not share a 1:1 model. Validators warn for web-only animation recipes instead of claiming parity.
3015
+
3016
+ ## Layered Backgrounds, Blobs, and Animation Cascade
3017
+
3018
+ The current background and motion stack combines app-level `LayerBackground`, animated blob layers, animation cascade, and safe CSSOM keyframe registration. Legacy background shorthands and per-element blob rendering are removed; `BackgroundStack` + `BlobLayer` (web + RN) mount at the renderer root from `tokens.identity.background`.
3019
+
3020
+ ### Cascade and blob contracts
3021
+
3022
+ 201. **`mergeElementAnimations(ctx: AnimationContext)` — pure cascade merge fn** — Exported from `mythik` via `packages/core/src/design/animation/cascade.ts`. Walks 4 levels (`identity → variant → template → element`) in order; later levels override earlier per-trigger. Returns `ElementAnimations | null` — null when every output trigger is absent or explicitly disabled. Null-stripping: triggers set to `null` are removed from the output object so consumers never null-check per-trigger. Compile-time exhaustiveness sentinel guards against future `AnimationTrigger` additions dropping silently.
3023
+
3024
+ 202. **`AnimationContext` type** — `{ identity?, variant?, template?, element? }` where each field is `ElementAnimations | null | undefined`. `undefined` = inherit upward (pass-through), `null` = explicit disable at this level, per-trigger values override earlier levels. Disjoint by contract from `ElementAnimations` (triggers mount/unmount/hover/focus/active/ambient/stateChange) — runtime discriminant by key presence.
3025
+
3026
+ 203. **Null semantics (decision table frozen)** — For any trigger across the cascade: (a) whole-field `null` at a level contributes nothing (inheritance-neutral, NOT a disable marker); (b) per-trigger `null` disables for that level AND blocks prior non-null values; (c) later non-null re-enables (later always trumps); (d) `undefined` = inheritance; (e) output strips nulls — callers see only defined triggers. Distinction is load-bearing: whole-field null vs per-trigger null have DIFFERENT semantics and must not conflate (tests in `packages/core/tests/design/animation/cascade.test.ts` pin the decision table).
3027
+
3028
+ 204. **`useElementAnimations` accepts `AnimationContext` OR `ElementAnimations`** — Both web and RN hooks accept `animations` as `ElementAnimations | AnimationContext | null | undefined`. Runtime discriminant via `isAnimationContext()` checks for `'identity' | 'variant' | 'template' | 'element'` keys. Dev mode throws on mixed-key inputs (shape with both context keys AND trigger keys) because these keysets are disjoint by contract; silent classification would drop half the input. Production preserves permissive behavior. Resolution is memoized by param identity.
3029
+
3030
+ 205. **Blob v2 catalog — `BLOB_CATALOG`** — Exported from `mythik`. `Record<CuratedBlobName, BlobShapeDef>` with 6 hand-curated entries: 5 organic (`organic-1..5`) + `circle`. Each entry has `name`, `viewBox: '0 0 W H'`, and a valid SVG `path` closed with `Z`. Custom user shapes use `shape: 'custom-svg'` (sentinel member of `BlobShapeName`, NOT in the catalog map). Aesthetic intent: balanced rounded, elongated diagonal, asymmetric multi-lobe (all-cubic to avoid T-reflection cusps), horizontal flowing ribbon, sharply asymmetric.
3031
+
3032
+ 206. **Blob v2 type surface** — `BlobShapeName`, `CuratedBlobName`, `BlobShapeDef`, `BlobPreset`, `BlobMotionPreset`, `BlobMotion`, `BlobInstance`, `BlobV2Config`, `BlobRenderStyle`, `BlobSpec` all exported from `mythik`. `BlobV2Config` accepts TWO mutually-exclusive forms (preset vs explicit `blobs[]`) — runtime-enforced at resolver level, type-level relaxed for JSON ergonomics. `(string & {})` branded intersection preserves literal autocomplete for `'primary'`/`'accent'` color keywords while accepting arbitrary hex strings. `BlobRenderStyle` makes `opacity` + `blur` REQUIRED (post-resolution, defaults applied); `BlobInstance` makes them optional (pre-resolution input).
3033
+
3034
+ 207. **`resolveBlobLayer(config, palette)` — pure fn, both forms** — Exported from `mythik` via `packages/core/src/design/background/blobs/resolver.ts`. Explicit form: for each `BlobInstance`, resolves palette keywords to hex, looks up catalog shapes, applies `opacity`/`blur` defaults using `??` (preserves explicit `0`), converts `BlobMotion` (drift/rotate/scale) to `ElementAnimations.ambient` inline animations. Preset form: hydrates curated `organic-duo` / `organic-trio` / `circle-pair` compositions from `PRESET_SEEDS`; `BlobMotionPreset` values (`drift-gentle`/`drift-fluid`/`drift-snappy`/`static`) map to `BlobMotion` via `PRESET_MOTION`. Ambiguous `{ preset, blobs }` prefers preset + dev-warn. Empty `palette: []` falls back to `['primary']` + dev-warn.
3035
+
3036
+ 208. **Custom-svg error policy (fail-loud dev, degrade prod)** — `shape: 'custom-svg'` requires both `path` and `viewBox` as non-empty strings. Dev (`NODE_ENV !== 'production'`): throws with field-specific message (`"custom-svg requires a non-empty path. Got path=undefined."`). Prod: skips the malformed blob + `console.warn`, preserving app stability. Rationale: specs in production should have passed validation; crashing the whole app over one bad blob is disproportionate. Ambiguity policy (`preset` + `blobs` both set) is weaker — only dev-warn, no throw — because there is a defensible winner (preset) and no rendering is broken.
3037
+
3038
+ 209. **Blob motion → `ElementAnimations.ambient` conversion** — Each `BlobMotion` dimension becomes one `InlineAnimation` on the `ambient` trigger: drift → 3-keyframe translate (`0%`→origin, `50%`→(range.x, range.y), `100%`→origin); rotate → 2-keyframe rotate (`from`→`to`); scale → 2-keyframe scale (`from`→`to`). All three use `direction: 'alternate'` + `iterations: 'infinite'`. Emission order: drift → rotate → scale. Single-dimension motion returns a scalar `AnimationRef`; multi-dimension returns an array — both valid per the `AnimationRef | AnimationRef[]` union. Perceived cycle duration: drift = 1× `duration` (symmetric trajectory), rotate/scale = 2× `duration` (forward + reverse iteration). Motion presets (`drift-gentle`: 28s/small/ease-in-out, `drift-fluid`: 20s/wide/ease-in-out, `drift-snappy`: 14s/medium/linear, `static`: no motion).
3039
+
3040
+ 210. **`validateBackgroundCaps(input)` — performance caps validator** — Exported from `mythik` alongside `validateAnimationCaps`. Input shape `{ blobs?, layers? }`; output shape `{ warnings: string[], errors: string[] }` (shared `AnimationValidationResult` type). Caps: blobs per background (soft 8 / hard 16), layers per background (soft 6 / hard 10), custom-svg path length (soft 500 / hard 2000 chars, only applied when `shape === 'custom-svg'` AND `path` is a string), motion dimensions per blob (soft 2 / hard 3). Threshold semantics are strictly-over (`>`).
3041
+
3042
+ 211. **`useShapeAnimations(ref, animations, options)` — Layer 3 web runner** — Exported from `mythik-react` via `packages/react/src/animation/useShapeAnimations.ts`. React hook for SVG-child animations (`<path>`/`<circle>`/`<rect>`/`<g>`…). Consumes the same `ElementAnimations` contract as `useElementAnimations` but narrowed to the `ambient` trigger ONLY; shape children have no hover/focus/active contract and no distinct mount ceremony. Attaches CSS animations via `el.style.animation` (surgical, preserves other inline styles); keyframes register once through the CSSOM singleton (zero `dangerouslySetInnerHTML`) and dedupe by hash so multiple shape instances with the same recipe share one CSS rule. Dev mode warns when non-ambient triggers are passed. Production silently ignores them. `options.recipes` SHOULD be stable for useMemo performance.
3043
+
3044
+ 212. **`useShapeAnimations(animations, options)` — Layer 3 RN runner** — Repository-preview implementation in `packages/react-native/src/animation/useShapeAnimations.ts`; it is not part of the initial npm publish surface. Reanimated parity of rule 211: returns `{ animatedProps }` that the consumer spreads onto `Animated.createAnimatedComponent(Path)` from `react-native-svg`. Uses the `HARD_PER_TRIGGER` (=6) fixed-pool `useSharedValue` pattern (`useSharedValueArray` helper, also exported) so React Hook count stays stable. Reuses `composeRNStyle(contributions, interpolate, interpolateColor)` shared with `useElementAnimations` — single composition pipeline for View-style and animated SVG props. Relies on `react-native-svg` v13+ auto-translating the transform array into SVG `transform="..."` strings. Same dev-mode non-ambient-trigger warning as the web hook.
3045
+
3046
+ 213. **Cross-platform Layer 3 parity pins** — `buildCSSKeyframes` (web) and `buildReanimatedSpec` (RN) interpret the same resolved `AnimationSpec` identically on load-bearing invariants: duration agreement (ms count matches regardless of input form `'28s'`/`'28000'`), keyframe stop count (web `%` markers match RN `inputRange` length), direction semantic (`'alternate'` token ⇔ `timing.reverse=true`), iterations `'infinite'`/numeric count, animated-prop enumeration (`translateX/Y`/`rotateDeg`/`scale`).
3047
+
3048
+ 214. **`Template.animations` field + `$prop`/`$state`/`$cond` interpolation** — `TemplateDefinition` supports `animations?: Record<string, unknown> | null`. Type is pre-interpolation (JSON shape with `$`-expression placeholders); runtime shape is `ElementAnimations` after `resolveDeepWithProps` traversal in the renderer template branch. Three-branch semantics: field absent = inheritance-neutral (no emission), whole-field `null` = forwarded verbatim (cascade-neutral with explicit intent), object = `$prop.X` / `$state.X` / `$cond`/`$then`/`$else` all resolve against merged template-defaults + consumer-element-props.
3049
+
3050
+ 215. **`ResolvedVariant.animations` passthrough with `$path` resolution** — `ResolvedVariant` supports `animations?: Record<string, unknown> | null`. `resolveVariant` routes the field through `resolveTokenRefs` (same mechanism as `style`/`hover`/`active`/`focus`) so `$path` references at the top level of each trigger value resolve (e.g. `animations: '$animations.interactive'`). Three-branch semantics mirror rule 214. `resolveTokenRefs` is one-level-deep: nested structures (`mount: { recipe: '$recipes.X' }`) pass through unchanged — downstream animation runtime layers are responsible for any deeper `$` resolution. Expression-system scope: only `$path` (token-tree) lookups are recognized here; `$prop`/`$state`/`$cond` are template-layer concepts.
3051
+
3052
+ 216. **End-to-end animation cascade at renderer time** — The render engine merges identity + variant + template + element animations via `mergeElementAnimations` and emits the merged result as `resolvedProps.animations` on the primitive render node. Box (web + RN) consumes the single final prop via `useElementAnimations`; no cascade plumbing lives on Box itself. Regular-element branches merge `identity + variant + element`; template branches merge `identity + template + element`; custom elements add the element-definition level described in rules 226-234. Element-level `animations` resolves through unified `resolveDeep` before merging so `$state`/`$cond`/`$token`/`$prop` expressions inside animation configs become concrete values. `scanDeps(element.animations)` tracks state changes deep inside animation expressions so they dirty the render cache. Whole-field `null` at identity level is cascade-neutral. Cached `resolvedProps` omit `animations` when no cascade level contributes, preventing stale-animation leakage across theme/spec changes.
3053
+
3054
+ 217. **`BlobsLayerConfig` + `resolveLayer` dispatch** — `LayerConfig` includes a blobs variant: `BlobsLayerConfig = LayerCommonProps & { type: 'blobs' } & Omit<BlobV2Config, 'opacity'> & { blobOpacity?: number }`. The `Omit<..., 'opacity'> + blobOpacity` rename resolves a semantic collision: `LayerCommonProps.opacity` is the layer-container wrapper alpha; `BlobV2Config.opacity` is the per-blob fill default. Keeping both as `opacity` at the same nesting level would compose multiplicatively. `LayerSpec` has a matching `{ kind: 'blobs'; config: BlobsLayerConfig; common: LayerCommon }` variant; unlike other `LayerSpec` kinds, the blobs variant carries unresolved config forward because palette/catalog/motion resolution needs tokens outside `resolveLayer` scope.
3055
+
3056
+ 218. **`BackgroundStack` palette threading + dispatch** — `BackgroundStack` (web + RN) accepts an optional `palette?: { primary: string; accent: string }` prop. The internal `renderLayer` helper routes blob layers with palette to `BlobLayer`; without a palette, a dev-mode warning fires instead of silently rendering an empty blob layer. A `toBlobV2Config` helper strips layer-level fields (`type`/`opacity`/`blendMode`/`zIndex`) and promotes `blobOpacity` → `BlobV2Config.opacity` at the render boundary, keeping the two opacity axes cleanly separated.
3057
+
3058
+ 219. **`BlobLayer` web — SVG blobs + `useShapeAnimations`** — `packages/react/src/background/BlobLayer.tsx`. Consumes `{ config: BlobV2Config, palette }` and emits one `<svg><path/></svg>` per resolved `BlobSpec`. The ref binds `useShapeAnimations` to the inner `<path>` (not the outer `<svg>`) so keyframe transforms apply to shape geometry; animating the wrapper would compose with the CSS position/size and produce visible positional drift under translate motion. JSX-native rendering throughout (no `dangerouslySetInnerHTML`). `resolveBlobLayer(config, palette)` is wrapped in `useMemo([config, palette])` so animation freshness does not force unnecessary resolve + keyframe-register work across all blobs.
3059
+
3060
+ 220. **`BlobLayer` RN — react-native-svg + Reanimated `useAnimatedProps`** — `packages/react-native/src/background/BlobLayer.tsx`. Mirrors web prop surface; platform deviations: (a) animation flows through `animatedProps` spread onto `Animated.createAnimatedComponent(Path)` instead of imperative `el.style.animation`; (b) opacity is a style prop, not an SVG attribute; (c) static rotation uses RN's transform-array shape `[{ rotate: deg }]`. `blob.blur` fires a dev warning and no filter is applied because RN filter parity is limited. `BackgroundStack` (RN) warns on non-`normal` blendMode because RN has no `mixBlendMode` equivalent. `resolveBlobLayer` is memoized for parity with web.
3061
+
3062
+ 221. **MythikRenderer root mount for `LayerBackground` v2** — When `tokens.identity.background` is a LayerBackground (`isLayerBackground` returns true — object carrying `color` or `layers`, no `style` field, rejects empty `{}` + arrays), the renderer wraps the rendered tree in a positioned container with `<BackgroundStack>` as a sibling inside a stacking context (`isolation: isolate`). The isolation context prevents per-layer `zIndex` values from leaking out to occlude the content wrapper. Palette threads from `tokens.colors.{primary, accent}`; missing/malformed colors fire a diagnostic dev warning at the renderer seam. Root wrappers carry `data-sv-renderer-root="v2"` (web) and `testID="sv-renderer-root-v2"` (RN) markers for test selection. `Spec.tokens?: Record<string, unknown>` is part of the public type graph so the renderer reads spec tokens without casts and the spec-validator accepts the field.
3063
+
3064
+ 222. **Legacy background + per-element blobs removed** — `resolveBackgroundCSS`, `resolveBlobStyles`, `BackgroundConfig`, `BlobStyle`, `background.ts`, and `tokens.backgroundCSS` injection are removed from `packages/core`. `Box` no longer has a `backgroundBlobs` prop and is now a pure surface-styled wrapper. App-level background lives exclusively at `tokens.identity.background`. `IdentityConfig.background` is `LayerBackground`; `DEFAULTS.identity.background` is `{}` (empty LayerBackground, no root mount by default). `useDesignTokens` (web + RN) reads `identity.background.color` + `identity.background.layers`, omitting undefined keys so `isLayerBackground` does not fire on dead data. The `isLayerBackground` rejection for objects with `style` remains as malformed-spec defense against legacy-shape holdovers.
3065
+
3066
+ 223. **`IdentityConfig.animations` on the type graph** — `IdentityConfig` includes `animations?: ElementAnimations | null`. TypeScript preset authors can declare `identity: { animations: {...} }` under `PresetDefinition.tokens.identity` without casts. Semantics: whole-field `null` is cascade-neutral, per-trigger `null` disables and blocks inheritance, absent = inheritance-neutral.
3067
+
3068
+ 224. **`/tokens/resolved` store path — reactive fully-resolved tokens** — Factory persists `resolvedTokens` to the store at `/tokens/resolved` at `createMythik` initialization and after every `updateTokens` call. Companion path `/tokens/raw` holds only the raw input accumulated through `updateTokens` (useful for export/inspection). `/tokens/resolved` holds the fully-resolved token tree: DNA-derived colors, DEFAULT-merged identity, and downstream consumer values. `MythikRenderer`'s v2 `LayerBackground` root mount reads from `/tokens/resolved` so preset swaps and playground control changes trigger fresh reads and remount the `BackgroundStack` with the new layers.
3069
+
3070
+ 225. **`Select` primitive listbox portals to `document.body`** — The Select dropdown's listbox panel (`packages/react/src/primitives/select.tsx`) renders via `React.createPortal(..., document.body)` instead of as a descendant of the select wrapper. Positioning uses `position: fixed` with coordinates tracked from the trigger button's `getBoundingClientRect()` and updated on open, scroll, and resize via `useLayoutEffect`. Outside-click detection checks both the wrapper ref and the listbox ref. Portaling to body escapes nested stacking contexts across the renderer tree, so the listbox competes with other top-level portals such as modals and toasts. `zIndex: 9999` is retained for safety above ambient overlay layers.
3071
+
3072
+ ### Known limitations
3073
+
3074
+ - The `getActiveTokens()` helper resolves theme tokens on every call — within one element render it may be called 2-3 times. Memoize per render-cycle if profiling shows it matters.
3075
+ - `instancePropsCache` diff uses shallow reference equality — object/array props sourced from `$state` (which produce new references each render) over-invalidate every render even when contents are equal. Deep-equality diff deferred pending profiling (see rule 233 + engine.ts docstring).
3076
+ - Template branch inside custom element expansion doesn't currently run the `variant` or `elementDef` cascade slots — only `identity + template + element`. Extending to full 5-level is a follow-up if a real need emerges.
3077
+
3078
+ ---
3079
+
3080
+ ## Layer 3 Custom Elements
3081
+
3082
+ 226. **Custom elements are first-class primitives** — Custom elements registered via `plugins.registerElement` participate in the full render contract. Consumers apply `animations`, `hover`, `active`, `focus`, `transition`, `motion`, `visible`, `key`, and `style` on the instance — identical syntax to built-in primitives. These apply to the OUTER primitive of the expansion (black-box boundary).
3083
+
3084
+ 227. **ElementRenderNode supports all Element declarative fields** — Authors declare `animations`, `hover`, `active`, `focus`, `transition`, `motion`, `visible`, `key` inside their render tree. Each field behaves identically to `Element` semantics for the primitive rendered at that position. `ElementRenderNode.children` type is `Array<ElementRenderNode | '$children'>` — string literal `'$children'` marks the slot position for consumer children.
3085
+
3086
+ 228. **ElementDefinition.variants + theme fallback** — Authors declare variants on the definition via `ElementDefinition.variants: Record<string, ElementVariantSpec>` where `ElementVariantSpec` has the same shape as primitive variant specs (`props`/`style`/`hover`/`active`/`focus`/`transition`/`animations`). Consumer's `{ variant: 'name' }` resolves: (1) `ElementDefinition.variants[name]` first, (2) `tokens.components[elementType].variants[name]` as theme-level fallback. Variant props merge into consumer props pre-expansion (consumer wins last, variant beats definition default).
3087
+
3088
+ 229. **5-level animation cascade: identity → variant → elementDef → template → element** — Animation cascade includes an `elementDef` level between variant and template. `elementDef` is the author's declaration on a primitive inside a custom element expansion (sourced from the expanded element's own `animations` field when propContext is active). Defaults to `undefined` (inheritance-neutral) outside custom elements. `mergeElementAnimations` accepts all 5 levels via the `AnimationContext` interface.
3089
+
3090
+ 230. **Custom element instance black-box — consumer affects outer only** — Consumer's instance-level declarations apply to the OUTER primitive of the expanded tree. Inner primitives are the author's domain. Inner primitives receive identity cascade, their own variant/elementDef declarations, and full expression resolution. Inner primitives do NOT receive consumer's instance-level `animations`/`hover`/`motion`/`style`/`visible`/`key`. A future `::part()`-style API can layer on top of this contract without breaking it.
3091
+
3092
+ 231. **`$prop` is nearest-enclosing-custom-element-scoped** — Inside a custom element's expanded tree, `$prop` references resolve against THAT custom element's `mergedProps`. Nesting a custom element pushes a new `propContext`; the outer context is shadowed, not merged. Thread values via explicit prop passthrough when nested access is needed. Matches CSS custom-property scoping semantics.
3093
+
3094
+ 232. **`$children` marker in custom element render trees** — Authors write `children: [..., '$children', ...]` in their render tree; consumer's children slot at each marker position during expansion via the `consumerChildrenIds` parameter of `expandElementToSpec`. Multiple markers each splice the full consumer children. Same slotting semantics as spec templates: consumer children retain their enclosing element IDs and render through unified dispatch.
3095
+
3096
+ 233. **Custom element cache invalidates on consumer prop changes via `/__prop/<name>` sentinels** — Cache dep scanner (`scanDeps` in `deps.ts`) tracks `$prop` references as `/__prop/<name>` sentinel paths. At custom-element dispatch time, an `instancePropsCache` diffs current vs previous `mergedProps`; changed keys are injected into the active changed-paths set (scoped to the expansion render via `try/finally`) so inner primitives that declared those prop refs correctly invalidate. Shallow reference equality can over-invalidate object/array props sourced from `$state`; see `engine.ts` docstring.
3097
+
3098
+ 234. **Custom element error boundary at instance level** — Errors anywhere inside a custom element's expansion (missing primitive, resolver throw, etc.) surface as an `_error` RenderNode caught by the `renderElement` `try/catch` wrapper. Consumer error handling does not need to know the author's internal structure; errors are isolated per instance so a broken custom element does not crash sibling renders.
3099
+
3100
+ 235. **Custom elements under `repeat`** — Custom elements dispatched under `element.repeat` (`count`, `statePath`, or `source`) render N instances as expected, same as primitives. Repeat handling applies before custom-element dispatch so the repeat gate fires for all element types uniformly. `$index` / `$item` context threads through consumer props into the expansion's `propContext` so inner primitives see the correct iteration values.
3101
+
3102
+ 236. **Custom element render trees can reference spec-level templates** — A custom element's render tree may use a type that resolves to a spec-level template (e.g. `render: { type: 'section-template' }`). The expansion (`expandElementToSpec`) propagates `spec.templates` and non-colliding `spec.elements` entries onto the expanded sub-spec so recursive template dispatch finds the definition. Template children references (e.g. `template.children: ['my-id']`) to outer-spec elements also resolve during recursion. Template's own `$prop` context is its own `mergedProps`, not the custom element's — standard template semantics are preserved.
3103
+
3104
+ 237. **`plugins.registerElement` exposed on `PluginLoader`** — `PluginLoader` exposes `registerElement(def: ElementDefinition)` and `getElements(): ElementDefinition[]`. Definitions are staged internally; `factory.applyPlugins()` iterates `plugins.getElements()` and calls `elements.register(def)` for each, skipping entries already present (idempotent with `config.elements`). `count()` includes the element definitions.
3105
+
3106
+ 238. **`validateSpec` accepts `elementRegistry` in `ValidationContext`** — An `el.type` not present in `primitiveRegistry` is no longer flagged as "unknown primitive type" when `elementRegistry` knows it. Suggestion candidates (for auto-fix Levenshtein matches) merge keys from both registries. `factory.ts` threads `elementRegistry: elements` through validation call sites so registered custom elements validate like built-ins.
3107
+
3108
+ 239. **Event bindings may be `$prop` expressions** — Inside a custom-element render tree, `element.on.<event>` can be `{ "$prop": "<propName>" }`. The renderer detects the `$prop` shape at binding level and calls `resolveDeep` to substitute the consumer's supplied value (typically an action array). Any other binding shape (plain object, array of actions, `TransactionBinding`) is passed through verbatim; inner `$state` / `$template` inside binding params stay lazy so the action dispatcher resolves them at press time. Eager param resolution only activates inside a `repeat` where `$item` binding is required for correctness.
3109
+
3110
+ 240. **Consumer `variant` lives under `props`** — The renderer reads the variant name from `resolvedProps.variant`. A top-level `variant` field on the element object is silently ignored. Applies uniformly to built-in primitives (button/box/etc. with `tokens.components` variants) and Layer 3 custom elements (with `ElementDefinition.variants`). Consumer specs: `{ "type": "stat-card", "props": { "variant": "primary", ... } }` — NOT `{ "type": "stat-card", "variant": "primary", "props": {...} }`.
3111
+ 241. **`/ui/selectedRow` is framework-reserved** — framework writes the clicked row to this path before dispatching row interactions (column actions in `columns[].actions[].onPress` AND direct `onRowClick`). Single-selection scope per screen. Both row interaction types use the same lazy-resolution contract — `$state` / `$template` in action `params` resolves at click time against the freshly-written row. See `ai-context-runtime-semantics.md § 2.1` for the row context model, write-owner (the `createRowDispatcher` helper at `packages/react/src/runtime/row-dispatcher.ts`), lifetime, read patterns, and multi-table caveat.
3112
+ 242. **Column action expressions resolve at press time via primitive `lazyActionPaths` declaration** — `$state`/`$template` inside `columns[].actions[].onPress` params resolve at click time, not render time. Any `$state` path returns its current value at press; framework writes the clicked row to `/ui/selectedRow` before dispatch (see rule 222 + runtime-semantics §5.1), so `$state: "/ui/selectedRow/*"` is a canonical pattern. Underlying mechanism: primitive declares `lazyActionPaths` in `PRIMITIVE_PROP_SCHEMAS` (table declares `['columns[].actions[].onPress']`); engine keeps subtree raw at render; dispatcher resolves at press. See `ai-context-runtime-semantics.md § 1.3`.
3113
+ 243. **CRUD endpoint generates 3 routes from 1 declaration** — an endpoint with `crud: { table, primaryKey, insertable, updatable }` generates `POST <path>`, `PUT <path>/:id`, `DELETE <path>/:id`. Do not declare 3 endpoints. See `ai-context-runtime-semantics.md § 3.1`.
3114
+ 244. **`/api/auth/login` expects `{ username, password }`** — not `email`. Use `loginBody` template to map consumer email convention → framework username. See `ai-context-runtime-semantics.md § 3.2`.
3115
+ 245. **Query endpoints envelope responses in `{ data: [...] }`** — shape is `{ data, total?, page?, pageSize?, totals? }`. Consumer specs read `response.data` via state; envelope is NOT auto-unwrapped. See `ai-context-runtime-semantics.md § 3.3`.
3116
+ 246. **Browser vs Server entries — `mythik` vs `mythik/server`** (v0.1.0). The default `mythik` entry is browser-safe by construction: zero transitive Node-only imports. `FileSpecStore`, `SqlServerSpecStore`, `SqlServerVersionedSpecStore`, `SqlServerEnvironmentStore` and their config types live in `mythik/server`.
3117
+
3118
+ 247. **`derive` and `dataSources` are processed at runtime per spec mount** (v0.1.0). When `spec.derive` is present, the framework instantiates a `DeriveEngine`, evaluates all derive paths in topological order at mount, and re-evaluates dirty paths reactively on state changes. Derive paths are protected: setState targeting a derive path errors at validate time and runtime. When `spec.dataSources` is present, the framework instantiates a `DataSourcesEngine`, performs initial fetches (deferred to reactive resolution when URL template deps are undefined), and re-fetches reactively when dependencies change. The action `refreshDataSource` (params: `{ id: string }`) is automatically registered for any spec with dataSources. URL templating requires the explicit `{ $template: '...' }` form — plain strings with `${...}` are NOT substituted (validator catches at load). See `ai-context-runtime-semantics.md` for lifecycle, ordering, error degradation, and state protection details.
3119
+
3120
+ 248. **CLI is the only approved path for spec writes** (v0.1.0). Three approved forms: (a) **Shell** - `mythik push <id> --from-file spec.json` / `mythik patch <id> --from-file patch.json` (or intentional stdin via `--from-file -` / pipe without `--from-file`); (b) **Bulk** - `mythik push --from-dir ./specs/` (sequential, continue-on-error, no rollback; partial state on failure recovers by fixing failures and re-running the same command); (c) **Programmatic** - `import { runPush, runPatch } from 'mythik-cli/api'` for IDE tooling, test harnesses, CI scripts. NEVER call `SpecStore.save()` directly from application code. It is an internal persistence primitive marked `@internal`; validation happens at the CLI / engine tier. Bypassing this path skips validation and can produce runtime errors visible only at render time. `mythik patch --from-file <path>` is preferred over inline JSON for any patch containing `$state`, `$template`, `$auth`, or `$row` references (PowerShell expands `$<word>` in double-quoted strings).
3121
+
3122
+ 249. **`mythik lint` detects known anti-patterns in specs and consumer code** (v0.1.0).
3123
+
3124
+ A new CLI command + programmatic API:
3125
+ - `mythik lint [--from-file <path> | --from-dir <folder>]` — bulk or targeted lint
3126
+ - `--specs-only` / `--code-only` — filter by surface
3127
+ - `--code-dir <path>` — override default code scan root (default: `./src`)
3128
+ - `--json` — machine-readable output
3129
+ - `import { runLint } from 'mythik-cli/api'` — programmatic equivalent
3130
+
3131
+ **4 rules in v0.1**:
3132
+ - `spec-row-literal` (warning) — `$row` is not an expression handler; use `$state: '/ui/selectedRow/<key>'`. Validator-resident in `spec-validator.ts` (also surfaces during `mythik push` and `mythik validate`).
3133
+ - `spec-crud-id-collision` (error) — `endpoint.path` ending `/:id` combined with `crud: {}` produces `/path/:id/:id`. Strip trailing `/:id`. Walks the object-shaped `endpoints: Record<string, EndpointConfig>`; emits JSON Pointer paths `/endpoints/<name>/path`.
3134
+ - `spec-auth-domains-port` (warning) — `auth.authDomains[i]` containing `:port` strips the port silently (matcher uses hostname-only via `URL.hostname`). Walks the `string[]` shape; emits JSON Pointer paths `/auth/authDomains/<index>`.
3135
+ - `code-store-save-bypass` (error) — calling `*store*.save()` outside `packages/core/` and `packages/cli/` bypasses validation. Use `runPush` from `mythik-cli/api` or `mythik push --from-file`. **Known scope**: matches `<Identifier>.save()` callees only (e.g. `myStore.save(...)`). Does NOT detect `this.store.save()`, `<obj>.<store>.save()`, or `(await getStore()).save()` patterns — these slip through the AST scanner. Generation-level guidance in `ai-context.md` (anti-patterns section) covers all variants; the lint rule is one layer of defense-in-depth, not a complete catch. Widening the scanner to handle `PropertyAccessExpression` callees is tracked as a v0.2+ candidate if real consumer code shows these patterns slipping through generation guidance.
3136
+
3137
+ **Code rules require TypeScript ^5.0.0 as peerDependency** (optional). If not installed, code rules emit one warning finding `lint-meta-no-typescript` (severity matches `LintSeverity = 'error' | 'warning'`) and skip cleanly. Spec rules run independently.
3138
+
3139
+ Spec rules are added to `spec-validator.ts` (rule 1) and `api-spec-validator.ts` (rules 2+3) and run on EVERY validation (push, validate, lint) — defense-in-depth. Code rules run only via `mythik lint` (no push integration; push doesn't see consumer code). `ApiSpecValidationResult` gains optional `lintWarnings?: ValidationError[]` field for rules 2+3 (additive non-breaking; preserves existing `errors: string[]` shape). `SpecValidationResult` also gains optional `lintWarnings?: ValidationError[]` to propagate findings across the `DocumentHandler.validate` boundary (`api-handler.validate` forwards `validateApiSpec().lintWarnings` so `mythik validate` and `mythik push` actually surface rules 2+3).
3140
+
3141
+ Exit codes: 0 = no errors, 1 = errors found, 2 = runtime error (e.g., unreadable file).
3142
+
3143
+ See `ai-context.md` for spec-gen anti-patterns the AI must NOT generate.
3144
+
3145
+ 250. **Storage tables are declarative, not auto-created** (v0.1.0). The framework's spec stores (`SqlServerSpecStore`, `SupabaseSpecStore`, and the versioned/environment subclasses) operate against three tables the consumer's database MUST already have: `screens` (base, REQUIRED for every adapter), `screen_versions` (opt-in, only for `*VersionedSpecStore`), and `screen_environments` (opt-in, only for `*EnvironmentStore`). The framework does NOT ship a database migration command in v0.1.0. Instead, the canonical schema is declared declaratively in `ai-context.md § Storage Setup`: per-table column specs (semantic types, nullability, defaults), constraints, indexes, Postgres-specific `jsonb` requirements, Postgres trigger requirements for `screens.updated_at` + `screens.version`, idempotency requirement, post-apply verification, custom table names, identifier-safety scope, and schema-evolution policy. The AI bootstrapping the consumer's environment reads the schema spec, translates it to the target SQL dialect (SQL Server, Postgres/Supabase, MySQL, etc.), applies idempotently (`IF NOT EXISTS`-style), and verifies via `INFORMATION_SCHEMA` queries. Future schema changes will be documented as additive ALTER instructions in the same ai-context section.
3146
+ 251. **`security.exposeErrors` controls render error detail** (v0.1.0). Default is `true`; set `createMythik({ security: { exposeErrors: false } })` for production-like hosts that must avoid leaking error messages/stacks. `_error` render nodes write diagnostics to `/ui/renderErrors` only when exposure is enabled. Primitive/component exceptions are caught by `MythikRenderer`'s error boundary: development + exposed mode shows an overlay with message and component stack; production or `exposeErrors: false` shows a neutral placeholder. The overlay resets when the spec changes so a corrected spec can recover without remounting the host.
3147
+ 252. **Icon packs register through `plugins.setIconRenderer`** (v0.1.0). Mythik does not bundle Phosphor/Lucide/etc. Register one host-level renderer from `MythikApp.onPlugins`; the built-in `icon` primitive keeps identity behavior and calls the renderer with `{ name, size, weight, color, style }`. If the placeholder circle renders, verify `onPlugins` registered the renderer and the consumer is not validating against stale tarballs or source aliases.
3148
+ 253. **`overridePrimitive('icon')` returns a RenderNode, not JSX** (v0.1.0). Use it only for full primitive replacement. The renderer function must return `{ type, props, children }` with `_component` in props when targeting React. Returning `<Icon />` directly is not the primitive renderer contract and bypasses the built-in icon identity wrapper.
3149
+ 254. **CLI explicit input wins over ambient stdin** (v0.1.0). For `mythik push`/`mythik patch`, `--from-file <path>` reads the file even if the host process exposes non-TTY stdin. Intentional stdin remains supported through `--from-file -` or by piping without an explicit file. `--from-file` plus a positional patch argument is still a conflict.
3150
+ 255. **`runPatch` versions in every output mode when `author` is provided** (v0.1.0). `SpecEngine.patch` applies and validates patches. CLI/API `runPatch` persists through the normal `store.save` path without `author`; with a versioned store + `author`, it captures the patched document and writes once through `saveVersion`, preserving lazy-bootstrap pre-patch history. JSON/TOON success output includes additive `versioned` and `version` metadata.
3151
+ 256. **DB-first specs require an explicit local sync step** (v0.1.0). In DB-first workflows, the database is source of truth and `specs/*.json` files are bootstrap/snapshot artifacts until refreshed. After `mythik patch`, run `mythik pull <id>` and write the stdout back to the local spec before committing or pushing from files; otherwise a later full `push` can overwrite DB-only patches.
3152
+ 257. **`/ui/selectedSpatialItem` is framework-reserved** - `spatial-map` writes activated item context for JSON `onItemPress` actions before dispatch. The payload is domain-neutral (`itemId`, `zoneId`, `status`, `position`, `rotation`, `shape`, `metadata`, full `item`, optional `zone`). `selectedItemPath` can override the path for isolated maps. Action params under `onItemPress` resolve lazily at press time via the primitive `lazyActionPaths` declaration, so `$state: "/ui/selectedSpatialItem/itemId"` reads the item just pressed. Programmatic function callbacks pass through without framework state writes, matching table callback semantics. Menus, drawers, modals, and inspectors are composed outside the primitive.
3153
+ 258. **`spatial-map` interaction hardening rules** - Canvas-level presses are handled at the SVG surface so empty zone areas can clear selection or dispatch `onCanvasPress`; item handlers stop propagation so item activation is not treated as a canvas press. Context dispatchers write context once, then pass ActionBinding arrays through unchanged so `MythikRenderer.dispatchAction` preserves serial async action-chain semantics. `interactionPolicy.selectItems` and `interactionPolicy.activateItems` are independent: selection can update `selectedItemPath` without dispatching `onItemPress`, including readonly maps explicitly opted into selection; activation controls action dispatch. In programmatic React mode, function callbacks pass through and consumers own selected visual state via `selectedItemId` or direct writes to `selectedItemPath`.
3154
+
3155
+ 259. **`/ui/spatialItemChange` is framework-reserved** - `spatial-map` writes item edit-change context before dispatching JSON `onItemChange`. The payload is domain-neutral and includes `changeType`, `itemId`, `previousItem`, `nextItem`, `previousPosition`, `position`, and `delta`. `itemChangePath` overrides the path for isolated maps. Programmatic callbacks pass through and own their own state.
3156
+
3157
+ 260. **`spatial-map` edit movement is controlled and data-first** - In `mode: "edit"`, drag preview is local and persistence occurs on drop through `onItemChange`/`onZoneChange`; keyboard arrows emit one change per valid move. Edit mode does not dispatch `onItemPress` by default; set `interactionPolicy.activateItems: true` only when edit-time activation is intentional. `editPolicy` controls movement, resize, bounds, keyboard steps, and coordinate precision. Current viewBox bounds clamp `item.position` to the `viewBox`; full collision and polygon/path point editing remain separate future primitive capabilities; item resize/rotate use `transform` plus `rotation`, and zone resize uses `transform.scaleX/scaleY` while preserving `shape`; snap/guides are configured through `editPolicy.snap` and `editPolicy.guides`. Create/duplicate/delete are composed generically today with `onCanvasPress`, `$uniqueId`, `$array`, and modal confirmation outside the primitive.
3158
+
3159
+ 261. **`/ui/spatialCanvasPress` is framework-reserved** - `spatial-map` writes SVG canvas press context before dispatching JSON `onCanvasPress`. The payload is domain-neutral with `kind: "canvas"`, `mode`, effective `point`, `rawPoint`, optional `snap`, `viewBox`, optional `zoneId`, optional `zone`. `canvasPressPath` overrides the path for isolated editors. Item handlers stop propagation, so item activation is not treated as a canvas press. Use this path to compose click-to-place workflows externally; the primitive does not create domain objects itself.
3160
+
3161
+ 262. **`$uniqueId` generates deterministic source-scoped ids** - Syntax: `{ "$uniqueId": true, "source": { "$state": "/items" }, "field": "id", "prefix": "item-", "padding": 2 }`. The handler scans the source array, stringifies existing field values, and returns the first unused prefixed number from `start` (default `1`). It is intended for JSON-composed local/editor objects and optimistic drafts; backend-owned database ids still come from the backend.
3162
+
3163
+ 263. **Conditional branches deep-resolve selected structured values** - `$cond` and `$switch` resolve nested expressions inside the selected object/array branch, while non-selected branches remain lazy. This allows action params such as `{ "$cond": ..., "$then": { "id": { "$uniqueId": true, ... }, "position": { "x": { "$state": "/ui/spatialCanvasPress/point/x" } } } }` to materialize as plain data before `setState`. Keep `$state` values as string paths; `{ "$state": { "$state": "/path" } }` is invalid.
3164
+
3165
+ 264. **`spatial-map` status is visual; `disabled` controls interactivity** - Item `status` values such as `available`, `blocked`, or `inactive` select semantic styling through `statusStyles` and remain selectable, activatable, draggable, keyboard-movable, duplicable, and deletable when the relevant policies allow those operations. Use `disabled: true` only for items that must be intentionally non-interactive. `canvasGuide` is the generic placement/inspection visual aid: drive `canvasGuide.visible` from consumer-owned state (for example `/ui/floorEditor/placeMode`) to render a dotted SVG crosshair at the current map pointer without mutating state on pointer move. Add/place still persists through `onCanvasPress` and `canvasPressPath`. When `editPolicy.snap` is enabled, the visual guide uses the same effective point that `onCanvasPress` writes to `/ui/spatialCanvasPress/point`.
3166
+
3167
+ 265. **`spatial-map` snap/guides are generic editor aids** - `editPolicy.snap` computes effective coordinates for item drag, zone drag, keyboard movement, item/zone resize handles, and canvas placement. Supported snap sources are grid snap and item-center snap. Grid threshold is per-axis, so X and Y can snap independently; item-center wins equal-distance ties over grid on the same axis. Keyboard movement with grid snap advances by grid stops on the moved axis. Zone movement snaps by the center of transformed derived zone bounds, or by `position` when no bounds exist. `editPolicy.guides` renders contextual authoring overlays such as snap lines and coordinates. The framework preserves JSON-first persistence: `onItemChange` emits snapped `nextItem.position`, `onZoneChange` emits snapped `nextZone.position` or resized `nextZone.transform`, and `onCanvasPress` writes `point` as the effective coordinate plus `rawPoint` as the original pointer coordinate. Snap/guide visuals do not create, move, or persist domain objects by themselves.
3168
+
3169
+ 266. **`spatial-map` resize uses item transform, not shape mutation** - Runtime resize writes `nextItem.transform.scaleX/scaleY` and keeps the base `shape` stable. Simple item shapes derive local edit bounds from geometry; arbitrary `path` items require `localBounds` for resize handles. This keeps one generic editing model across rect, circle, ellipse, polygon, and path shapes and avoids repeated lossy rewrites of polygon points or SVG path data.
3170
+
3171
+ 267. **`spatial-map` rotate/resize emit lazy item-change contexts** - Pointer resize/rotate preview locally and commit once on pointer release through `onItemChange`; keyboard resize/rotate emit one change per command. Resize contexts include `previousTransform`, `transform`, `previousLocalBounds`, and `localBounds`; rotate contexts include `previousRotation` and `rotation`; all runtime edits include complete `previousItem` and `nextItem`. Context transforms are normalized with the active edit policy. JSON specs persist edits with `$array: "replace"` from `/ui/spatialItemChange/nextItem` or the configured `itemChangePath`; wrap that same value in `editorCommit` when the screen needs undo/redo/dirty tracking.
3172
+
3173
+ 268. **`/ui/selectedSpatialZone` is framework-reserved** - `spatial-map` writes selected/activated zone context for JSON `onZonePress` actions before dispatch. The payload is domain-neutral (`kind: "zone"`, `mode`, `zoneId`, optional `label`, `position`, `rotation`, optional `transform`, optional `localBounds`, `shape`, `metadata`, full `zone`). `selectedZonePath` can override the path for isolated maps. Programmatic callbacks pass through and consumers own selected visual state with `selectedZoneId` or direct writes to `selectedZonePath`.
3174
+
3175
+ 269. **`/ui/spatialZoneChange` is framework-reserved** - `spatial-map` writes zone edit-change context before dispatching JSON `onZoneChange`. Runtime zone movement emits `changeType: "move"` and writes `nextZone.position`; runtime zone resize emits `changeType: "resize"` and writes `nextZone.transform.scaleX/scaleY`. Both include complete `previousZone`/`nextZone` records and preserve `nextZone.shape`, which keeps polygon/path geometry stable and leaves vertex/path point editing as a separate capability. Drag, keyboard movement, and zone resize handles use the same snap/guides resolver pattern as items; the movement snap anchor is the center of transformed derived zone bounds, or `position` when no bounds exist. Persist with `$array: "replace"` from `/ui/spatialZoneChange/nextZone` or the configured `zoneChangePath`; wrap that same value in `editorCommit` when the screen needs undo/redo/dirty tracking.
3176
+
3177
+ 270. **Gradient cards** — `identity.gradients.cards = true | 'vibrant' | 'soft' | 'muted'` applies a primary→surface diagonal gradient on box surfaces with `surface: "card"`. Implemented via `applyGradientCardsCSS` at the surface CSS layer (mirrors `applyBorderElevationCSS` architecture) so primitives consume `surface.card` uniformly without per-component branching. Uses `backgroundImage` longhand to compose with the surface treatment's `backgroundColor` cleanly. Spec `style.background`/`backgroundImage`/`backgroundColor` overrides
3178
+
3179
+ 271. **Modal portals to document.body** — Modal primitive wraps its render in `createPortal(tree, document.body)` so `position: fixed` escapes containing block traps. Required because CSS mount animations with `fill-mode: both` leave a final transform on ancestor elements, and any non-`none` transform on an ancestor scopes `position: fixed` to that ancestor instead of the viewport. SSR-safe via `typeof document === 'undefined'` fallback that returns the inline tree
3180
+
3181
+ 272. **Gradient styles use `backgroundImage` longhand** — Spec elements that compose with framework surface treatments must use `backgroundImage` (not `background` shorthand) for gradient values. React warns about mixing shorthand `background` with longhand `backgroundColor` from framework `surface.*` styles. `applyGradientCardsCSS`, `text.tsx` gradient text, and `button.tsx` primary gradient all use `backgroundImage` to compose cleanly. For solid colors, use `backgroundColor` directly
3182
+
3183
+ 273. **`editorSessions` declares generic undoable document paths** - `Spec.editorSessions` mounts framework-managed editor history for explicitly tracked document paths. Example: `"editorSessions": { "floor-layout": { "paths": ["/layout/items", "/layout/zones"], "maxHistory": 50 } }`. Paths must be non-empty absolute state paths and must not live under `/ui`; keep transient drafts, selected objects, modals, and placement modes consumer-owned outside the tracked document set.
3184
+
3185
+ 274. **`/ui/editorSessions/<id>` is framework-reserved metadata** - Mounted editor sessions write readable metadata under `/ui/editorSessions/<id>`: `dirty`, `canUndo`, `canRedo`, `undoDepth`, `redoDepth`, `revision`, `savedRevision`, `lastCommitLabel`, `lastSavedAt`, `status`, `saveStatus`, `saveError`, `lastSaveAttemptAt`, and `validation`. Consumers may read this metadata for toolbar state and save banners; consumers should not write it directly. `lastCommitLabel` reflects the current top undo entry and is cleared when no undo entry remains.
3186
+
3187
+ 275. **Use `editorCommit` for undoable document edits** - `editorCommit` applies one atomic user edit to a configured session and records previous/next values for undo/redo. Use it instead of plain `setState` when an edit should be undoable. Spatial editors persist item/zone changes by wrapping the usual `$array: "replace"` value in an `editorCommit` change targeting `/layout/items` or `/layout/zones`. `editorCommit` with `validate: true` validates after applying the edit and does not roll back automatically.
3188
+
3189
+ 276. **Save transport remains backend-owned; `editorSave` is the normal framework path** - The framework does not provide a database adapter, but editor sessions can declare generic persistence through `editorSessions.<id>.persistence` and save with `editorSave`. Use `editorMarkSaved` only as a low-level escape hatch after an advanced/custom persistence step has already succeeded. `editorDiscard` restores the last saved snapshot and clears undo/redo. Dirty state is computed from tracked path equality against the saved snapshot, not from undo-stack length. Commit, undo, redo, and discard clear stale validation metadata and stale save errors unless validation is explicitly requested again.
3190
+
3191
+ 277. **`editorSessions.<id>.persistence` declares generic editor save transport** - Configure `url`, optional `method`, optional `headers`, optional `body`, optional `target`, and optional `timeout` on an editor session. Default method is `PUT`; default body is `trackedPaths`, which sends the tracked document paths as a nested object preserving state tree shape. Session headers and per-call headers are merged, with per-call keys winning. Use `body: "snapshot"` when the backend wants absolute-path keys under `paths`, or a custom object body when the app needs a specific envelope.
3192
+
3193
+ 278. **`editorSave` is the recommended editor session save action** - Use `{ "action": "editorSave", "params": { "session": "<id>" } }` instead of hand-composing `transaction` + `editorMarkSaved`. The framework captures the in-flight snapshot, persists it through the host fetcher with URL-guard checks, marks only that snapshot as saved after success, and leaves later edits dirty if the user changed the document while save was in flight. Failed saves never mark clean; they expose `saveStatus`, `saveError`, and `lastSaveAttemptAt` under `/ui/editorSessions/<id>`.
3194
+ 279. **`navigation.editorSessionGuard` protects dirty editor sessions during app navigation** - App specs can configure `navigation.editorSessionGuard` with `enabled`, optional `sessions`, optional `blockNavigation`, optional `blockGoBack`, optional `blockBrowserUnload`, and optional `pendingPath`. The guard reads framework metadata under `/ui/editorSessions/<id>` and writes pending state to `/ui/navigationGuard/pending` by default. If `sessions` is omitted, all mounted editor sessions are considered.
3195
+
3196
+ 280. **Dirty-guard proceed is non-destructive; save/discard choices are explicit** - `navigationGuardCancel` clears the pending guard state and stays on the current screen. `navigationGuardSaveAndProceed` is the normal save-first guard action: it calls the mounted `EditorSessionEngine.save({ session })` for each pending dirty editor session, then resumes only if those sessions become clean. `navigationGuardProceed` is a low-level retry that resumes a pending navigation only after the pending editor sessions are already clean; it does not save or discard tracked-path changes. `navigationGuardDiscardAndProceed` delegates to `EditorSessionEngine.discard(sessionId)` for each pending dirty session, then resumes. Consumer JSON should render confirmation UI from `/ui/navigationGuard/pending` and keep custom `pendingPath` values under non-reserved `/ui/<segment>` paths.
3197
+
3198
+ 281. **`MythikApp.fetcher` supplies editor persistence when auth fetch is absent** - React hosts can pass `fetcher={(url, options) => ...}` to `MythikApp` so `editorSave` and `navigationGuardSaveAndProceed` have a transport in non-auth apps. When auth is configured, the framework fetch with auth interceptors remains the active fetcher. Save actions do not read global `/ui/lastError`; editor save status, errors, and attempts are reported under `/ui/editorSessions/<id>`.
3199
+
3200
+ 282. **Existing spec edits must use the CLI inspection-then-patch loop** - For an existing screen/app/api spec, AI agents should run `mythik manifest <id>` first, inspect only the target nodes with `mythik elements <id> <ids>`, apply a small RFC 6902 patch with `mythik patch <id> --from-file patch.json`, and verify with `manifest` or `elements`. This keeps edits surgical, validated, versionable, and reviewable. `pull` is for backup, migration, review, or full-document work; `push` is for new specs or intentional full replacement. Direct database edits and direct `SpecStore.save()` calls bypass validation and are not approved spec-write paths.
3201
+ 283. **Bundled AI docs are part of the install surface** - The `mythik` package includes `docs/llms.txt`, `docs/consumer/*`, and `docs/wiki/compiled/*`. Agents should run `mythik docs path` before spec generation and read the bundled docs rather than guessing from package source. Use `mythik docs copy ./mythik-docs` to create a project-local copy when the host workflow needs explicit files.