lecodes-sdk 1.1.0 → 2.0.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 (257) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +11 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Particles.d.ts +15 -0
  25. package/dist/types/gl/Scene.d.ts +41 -13
  26. package/dist/types/gl/Texture.d.ts +1 -1
  27. package/dist/types/gl/animation/Locomotion.d.ts +59 -3
  28. package/dist/types/gl/terrain/Terrain.d.ts +8 -0
  29. package/dist/types/inject.d.ts +11 -11
  30. package/dist/types/inject.editor.d.ts +1 -0
  31. package/dist/types/net/core.d.ts +7 -0
  32. package/dist/types/plugin.d.ts +76 -0
  33. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  34. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  35. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  36. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  37. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  38. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  39. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  40. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  41. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  42. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  43. package/dist/types/runtime/app.d.ts +9 -2
  44. package/dist/types/runtime/fetch.d.ts +2 -0
  45. package/dist/types/runtime/input.d.ts +8 -1
  46. package/dist/types/runtime/media.d.ts +6 -10
  47. package/dist/types/runtime/misc.d.ts +4 -1
  48. package/dist/types/runtime/net.d.ts +3 -2
  49. package/dist/types/runtime/touch.d.ts +32 -0
  50. package/dist/types/scene/defineScene.d.ts +43 -2
  51. package/dist/types/scene/editor.d.ts +52 -0
  52. package/dist/types/scene/gizmos.d.ts +7 -4
  53. package/dist/types/ui/NativeView.d.ts +6 -4
  54. package/dist/types/ui/UI.d.ts +1 -1
  55. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  56. package/dist/types/ui/UIButton.d.ts +12 -14
  57. package/dist/types/ui/UIContainer.d.ts +0 -6
  58. package/dist/types/ui/UIImage.d.ts +1 -4
  59. package/dist/types/ui/UIInput.d.ts +8 -24
  60. package/dist/types/ui/UIModal.d.ts +0 -2
  61. package/dist/types/ui/UINode.d.ts +70 -56
  62. package/dist/types/ui/UIPager.d.ts +28 -27
  63. package/dist/types/ui/UIPopover.d.ts +0 -2
  64. package/dist/types/ui/UIScreen.d.ts +12 -17
  65. package/dist/types/ui/UIScrollable.d.ts +1 -4
  66. package/dist/types/ui/UIText.d.ts +0 -2
  67. package/dist/types/ui/UIVideo.d.ts +3 -5
  68. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  69. package/dist/types/ui/UIWidget.d.ts +10 -9
  70. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  71. package/dist/types/ui/presentable.d.ts +46 -32
  72. package/dist/types/ui/router.d.ts +18 -7
  73. package/dist/types/ui/styleColor.d.ts +1 -0
  74. package/dist/types/ui/transitions.d.ts +18 -0
  75. package/dist/types/ui/tree.d.ts +75 -0
  76. package/dist/types/version.d.ts +10 -0
  77. package/dist/types.json +1 -1
  78. package/package.json +12 -3
  79. package/prompts/2d.md +2 -6
  80. package/prompts/3d.md +1 -5
  81. package/prompts/README.md +142 -142
  82. package/prompts/canvas.md +9 -8
  83. package/prompts/compose.ts +1 -1
  84. package/prompts/core.md +3 -3
  85. package/prompts/design.md +1 -1
  86. package/prompts/dist/2d-game.md +77 -54
  87. package/prompts/dist/3d-app.md +75 -52
  88. package/prompts/dist/ar-app.md +74 -51
  89. package/prompts/dist/design.md +30 -8
  90. package/prompts/dist/ui-app.md +73 -46
  91. package/prompts/ui-design.md +2 -3
  92. package/prompts/ui.md +45 -37
  93. package/src/animate/tween/Animation.ts +262 -378
  94. package/src/animate/tween/animateValue.ts +103 -100
  95. package/src/animate/tween/easing.ts +10 -3
  96. package/src/animate/tween/spec.ts +505 -479
  97. package/src/audio/Sound.ts +3 -3
  98. package/src/audio/audio.ts +162 -161
  99. package/src/bridges/2d.d.ts +317 -0
  100. package/src/bridges/app.d.ts +91 -0
  101. package/src/bridges/audio.d.ts +97 -0
  102. package/src/bridges/canvas.d.ts +79 -0
  103. package/src/bridges/device.d.ts +72 -0
  104. package/src/bridges/fetch.d.ts +80 -0
  105. package/src/bridges/files.d.ts +70 -0
  106. package/src/bridges/gl.d.ts +1133 -0
  107. package/src/bridges/input.d.ts +72 -0
  108. package/src/bridges/media.d.ts +55 -0
  109. package/src/bridges/nav.d.ts +71 -0
  110. package/src/bridges/net.d.ts +52 -0
  111. package/src/bridges/service.d.ts +52 -0
  112. package/src/bridges/socket.d.ts +31 -0
  113. package/src/bridges/storage.d.ts +33 -0
  114. package/src/bridges/tree.d.ts +301 -0
  115. package/src/bridges/types.d.ts +49 -0
  116. package/src/canvas/Canvas.ts +114 -159
  117. package/src/canvas/gen/cssColor.ts +224 -0
  118. package/src/canvas/gen/recorder.ts +212 -0
  119. package/src/canvas/gen/spec.ts +201 -0
  120. package/src/chisel.ts +193 -0
  121. package/src/compile/assetMacro.ts +1 -1
  122. package/src/compile/bundler.ts +11 -2
  123. package/src/compile/compileProject.ts +43 -4
  124. package/src/compile/fontMacro.ts +3 -4
  125. package/src/compile/header.ts +26 -5
  126. package/src/compile/index.ts +3 -1
  127. package/src/compile/liteMaterial.ts +1 -1
  128. package/src/compile/sceneEditor.ts +11 -26
  129. package/src/core/color.ts +73 -30
  130. package/src/core/pins.ts +51 -0
  131. package/src/core/signals.ts +8 -1
  132. package/src/g2/CharacterController2D.ts +3 -3
  133. package/src/g2/Node2D.ts +57 -39
  134. package/src/g2/Physics2D.ts +2 -2
  135. package/src/g2/Scene2D.ts +35 -23
  136. package/src/g2/Texture2D.ts +1 -1
  137. package/src/g2/loop.ts +4 -4
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +13 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Model.ts +172 -167
  144. package/src/gl/Node.ts +91 -24
  145. package/src/gl/Particles.ts +20 -1
  146. package/src/gl/Scene.ts +100 -44
  147. package/src/gl/Texture.ts +8 -7
  148. package/src/gl/animation/AnimationClip.ts +1 -1
  149. package/src/gl/animation/Locomotion.ts +77 -8
  150. package/src/gl/nav/NavMesh.ts +3 -4
  151. package/src/gl/physics/Physics.ts +2 -2
  152. package/src/gl/physics/physicsEvents.ts +3 -3
  153. package/src/gl/terrain/Terrain.ts +33 -5
  154. package/src/gl/touch.ts +14 -15
  155. package/src/host.d.ts +2 -3
  156. package/src/inject.editor.ts +7 -0
  157. package/src/inject.ts +233 -236
  158. package/src/net/core.ts +6 -5
  159. package/src/net/index.ts +1 -1
  160. package/src/net/replication.ts +1 -1
  161. package/src/plugin.ts +191 -0
  162. package/src/plugins/gen/camera/contract.d.ts +27 -0
  163. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  164. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  165. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  166. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  167. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  168. package/src/plugins/gen/map/contract.d.ts +144 -0
  169. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  170. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  171. package/src/plugins/gen/push/contract.d.ts +61 -0
  172. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  173. package/src/plugins/gen/push/sdk/push.ts +105 -0
  174. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  175. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  176. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  177. package/src/plugins/permission.ts +5 -4
  178. package/src/runtime/app.ts +20 -9
  179. package/src/runtime/appEvents.ts +5 -4
  180. package/src/runtime/channel.ts +18 -15
  181. package/src/runtime/clipboard.ts +4 -3
  182. package/src/runtime/datetime.ts +2 -1
  183. package/src/runtime/device.ts +17 -15
  184. package/src/runtime/fetch.ts +30 -20
  185. package/src/runtime/files.ts +16 -15
  186. package/src/runtime/input.ts +21 -8
  187. package/src/runtime/media.ts +50 -46
  188. package/src/runtime/misc.ts +7 -3
  189. package/src/runtime/net.ts +8 -7
  190. package/src/runtime/rpc.ts +1 -3
  191. package/src/runtime/service.ts +19 -14
  192. package/src/runtime/share.ts +4 -3
  193. package/src/runtime/storage.ts +6 -4
  194. package/src/runtime/touch.ts +32 -0
  195. package/src/scene/defineScene.ts +61 -365
  196. package/src/scene/editor.ts +408 -0
  197. package/src/scene/editorPlugins.ts +3 -3
  198. package/src/scene/gizmos.ts +154 -148
  199. package/src/server/db/marci/query.ts +1 -1
  200. package/src/server/host.ts +1 -1
  201. package/src/server/runtime.ts +1 -1
  202. package/src/ui/NativeView.ts +72 -25
  203. package/src/ui/UI.ts +3 -3
  204. package/src/ui/UIBottomSheet.ts +16 -17
  205. package/src/ui/UIButton.ts +54 -16
  206. package/src/ui/UIContainer.ts +0 -6
  207. package/src/ui/UIImage.ts +34 -30
  208. package/src/ui/UIInput.ts +29 -37
  209. package/src/ui/UIModal.ts +1 -3
  210. package/src/ui/UINode.ts +347 -297
  211. package/src/ui/UIPager.ts +93 -78
  212. package/src/ui/UIPopover.ts +0 -2
  213. package/src/ui/UIScreen.ts +41 -36
  214. package/src/ui/UIScrollable.ts +19 -13
  215. package/src/ui/UISpacer.ts +1 -1
  216. package/src/ui/UITabs.ts +8 -6
  217. package/src/ui/UIText.ts +7 -17
  218. package/src/ui/UIVideo.ts +30 -27
  219. package/src/ui/UIVirtualizedList.ts +58 -59
  220. package/src/ui/UIWidget.ts +38 -18
  221. package/src/ui/colorKeys.gen.ts +37 -0
  222. package/src/ui/fonts.ts +2 -2
  223. package/src/ui/presentable.ts +59 -42
  224. package/src/ui/router.ts +52 -35
  225. package/src/ui/styleColor.ts +56 -0
  226. package/src/ui/theme.ts +7 -6
  227. package/src/ui/transitions.ts +249 -0
  228. package/src/ui/tree.ts +346 -0
  229. package/src/version.ts +24 -0
  230. package/tests/helpers/engineWorld.ts +11 -0
  231. package/tests/helpers/fakeTree.ts +353 -0
  232. package/tests/helpers/hostStubs.ts +31 -0
  233. package/tests/helpers/index.ts +14 -0
  234. package/tests/helpers/memoryMarci.ts +124 -0
  235. package/tests/helpers/phases.ts +23 -0
  236. package/tests/helpers/preload.ts +18 -0
  237. package/tests/helpers/stubApp.ts +2 -0
  238. package/tests/helpers/stubDevice.ts +2 -0
  239. package/tests/helpers/stubFetch.ts +2 -0
  240. package/tests/helpers/stubInput.ts +2 -0
  241. package/dist/inject.js +0 -4730
  242. package/dist/types/core/registry.d.ts +0 -7
  243. package/dist/types/plugins/camera.d.ts +0 -25
  244. package/dist/types/plugins/push.d.ts +0 -46
  245. package/src/bridges.d.ts +0 -1760
  246. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  247. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  248. package/src/compile/__tests__/assetName.test.ts +0 -55
  249. package/src/compile/__tests__/compile.test.ts +0 -310
  250. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  251. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  252. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  253. package/src/core/__tests__/stateMachine.test.ts +0 -132
  254. package/src/core/registry.ts +0 -23
  255. package/src/plugins/camera.ts +0 -81
  256. package/src/plugins/push.ts +0 -132
  257. package/src/plugins/qr.ts +0 -73
@@ -24,18 +24,43 @@ Remove a single top-level declaration:
24
24
 
25
25
  <remove file="home.ts" signature="let lastTime" />
26
26
 
27
+ Replace an exact snippet INSIDE a file — the operation for a small change deep in a large declaration (a screen function, a scene setup), where <edit> would re-emit hundreds of untouched lines:
28
+
29
+ <replace file="screens/group.ts">
30
+ <old>
31
+ UIText("Balance", { size: 16 }),
32
+ </old>
33
+ <new>
34
+ UIText("Group balance", { size: 18, weight: "bold" }),
35
+ </new>
36
+ </replace>
37
+
27
38
  Rules:
28
39
  - <file> replaces the whole file — write it out in full, never elide with "// ... rest unchanged"
40
+ - <replace>: <old> is copied VERBATIM from the file as it is now (same characters, same lines — indentation is forgiven, nothing else is) and must occur exactly once — quote enough surrounding lines to make it unique; <new> is what takes its place and is never empty (to delete lines, quote the surrounding lines in <old> and repeat them without the deleted ones in <new>). One <replace> per spot; several spots in one file = several <replace> tags, each quoting the file as it was before your reply (they apply in order, so never let one <old> depend on an earlier <new>)
41
+ - A <replace> ALWAYS holds both parts INSIDE the same tag, in this order: <old>…</old> then <new>…</new>. A <replace> without those inner tags is invalid and applies nothing — never emit the old text in one <replace> and the new text in a second one
29
42
  - <edit> and <remove> target exactly ONE top-level declaration (function / const / let in global scope). The signature is matched against the start of the existing declaration; the operation then covers that whole declaration — from its first line to its end (closing brace for functions/objects) — and nothing else: never neighboring declarations or surrounding comments
30
43
  - The signature only needs the declaration's keyword and name (`function search`, `const label`) — anything after the name is ignored. It must name a declaration that exists in the file right now
31
44
  - <edit> must contain the complete new declaration, not a fragment. The new version may differ in name or arguments (renames are allowed — the signature points at the old declaration). After a rename or argument change, update every call site, each via its own <edit>
32
45
  - One tag per declaration. To change or remove several declarations, emit several tags
33
46
  - Removing a variable? Also update every declaration that references it (each via its own <edit>)
34
- - Pick <edit>/<remove> for tweaking a few existing declarations; <file> when adding declarations, changing imports, or restructuring
35
- - If a change doesn't fit these operations, fall back to <file> — never bend <edit> to cover multiple declarations
47
+ - Choosing the operation, cheapest first: <replace> for a change of a few lines anywhere (a value, a label, one call, one branch); <edit>/<remove> when a whole small declaration changes shape; <file> for new files and real restructuring. Never bend <edit> to cover multiple declarations; if nothing else fits, fall back to <file>
48
+ - What you write is the expensive part of a turn. Never re-emit a file the request didn't change, and never rewrite a whole file — or a whole 100-line function — to touch a few lines: that is what <replace> is for. When a large existing file needs a NEW declaration, prefer putting it in a new small file (imports between project files are managed for you) over rewriting the large one
36
49
  - File name includes the path if nested: "pages/home.ts"
37
50
  - Only raw file content inside tags, no markdown fences (```). Backticks for template literals in the code are fine, and non-TS assets (e.g. raw SVG XML in a .svg file) are allowed.
38
51
 
52
+ // === EXAMPLE: a small change inside a big screen ===
53
+ // screens/home.ts is a 120-line `export function homeScreen()`. Task: make the title bigger.
54
+
55
+ <replace file="screens/home.ts">
56
+ <old>
57
+ UIText("My cats", { size: 20, weight: "bold" }),
58
+ </old>
59
+ <new>
60
+ UIText("My cats", { size: 28, weight: "bold" }),
61
+ </new>
62
+ </replace>
63
+
39
64
  // === EXAMPLE: partial edits ===
40
65
  // Existing file has: let debugMode = true; const formatCount = (n) => `Count: ${n}`; const label = UIText(formatCount(0))
41
66
  // Task: rename formatCount → formatLabel with a prefix arg, drop unused debugMode:
@@ -58,7 +83,11 @@ directive and nothing else:
58
83
  <bundle>ar</bundle> (or <bundle>3d</bundle> / <bundle>2d</bundle>)
59
84
 
60
85
  The platform reloads your instructions with the right engine documentation and repeats the request
61
- automatically. Never use this when the needed APIs are documented here — just do the work.
86
+ automatically. This is also how a project CHANGES engine: a 2D game the user now wants in 3D, a 3D
87
+ scene they want as a flat 2D game — ask for the engine the request needs, then rewrite what must
88
+ change. Never tell the user the platform cannot do it, and never substitute a static picture or a
89
+ fake for the engine they asked for. Never use this when the needed APIs are documented here — just
90
+ do the work.
62
91
 
63
92
  ## Modes
64
93
 
@@ -81,7 +110,7 @@ Everything in this prompt is a global — no imports needed. A project's only im
81
110
 
82
111
  `main.ts` is the entry point — always name the entry file `main.ts`. A multi-file app's entry just wires things together: import the other modules, then run the launch logic (Router.init(...) / scene.open()). Every other file must be reachable from `main.ts` through imports — a side-effect module (registration code, global setup) still needs an `import './that-file'` in the entry, or it never runs. (In a project without a `main.ts`, the file nothing else imports is treated as the entry.)
83
112
 
84
- Imports of the project's own files are also managed automatically: if your <edit> makes code reference another file's export, the import is added for you — never fall back to a whole <file> rewrite just to change import lines. When writing a complete <file>, include imports normally.
113
+ Imports of the project's own files are also managed automatically: if your <edit> or <replace> makes code reference another file's export, the import is added for you — never fall back to a whole <file> rewrite just to change import lines. When writing a complete <file>, include imports normally.
85
114
 
86
115
  Reference a project asset (image, font, video, .svg, .glb, …) by importing it or with the inline `asset('./path')` macro — a compile-time equivalent of the import (string LITERAL only, never a variable). External `https://…` URLs are used directly as strings.
87
116
 
@@ -91,11 +120,11 @@ Each request carries the project state: `[Assets]` lists binary files by path; `
91
120
 
92
121
  ## Automatic reports
93
122
 
94
- A user message starting with `[Automatic report]` is machine-generated feedback from the platform, not the user — e.g. a runtime error thrown by the running app, with a source-mapped stack. Fix the problem directly with <file>/<edit> operations. At most one short sentence of explanation; never apologize or ask for confirmation.
123
+ A user message starting with `[Automatic report]` is machine-generated feedback from the platform, not the user — e.g. a runtime error thrown by the running app, with a source-mapped stack. Fix the problem directly with <replace>/<edit>/<file> operations. At most one short sentence of explanation; never apologize or ask for confirmation. A report that a <replace> or <edit> did NOT apply means the file is unchanged there: re-quote the <old> text exactly from the current file, or name an existing declaration — don't repeat the same tag.
95
124
 
96
125
  After every edit you make, the platform automatically verifies it — syntax, a full compile, and (for UI apps) a silent run that catches startup crashes — and sends any failure back as an `[Automatic report]`. Lesser findings (type errors, a blank first screen) are shown to the user, who can send them as a report with one tap. So:
97
126
  - A report is a checker result, not a person. Fix exactly what it lists; don't re-explain the whole change.
98
- - If a report repeats an error you already tried to fix, your last approach didn't work — take a DIFFERENT one. Prefer rewriting the whole file with `<file>` over another targeted `<edit>`.
127
+ - If a report repeats an error you already tried to fix, your last approach didn't work — take a DIFFERENT one. Prefer rewriting the whole file with `<file>` over another targeted `<edit>`/`<replace>`.
99
128
  - A report may include the JSON of what actually rendered (the first screen) — read it to fix a blank or broken screen instead of guessing.
100
129
  - If a report says your reply was cut off and asks you to continue, re-emit the interrupted `<file>` block from its very beginning (a re-opened `<file>` replaces the whole file — never continue a file mid-line).
101
130
  - Prefer several small files over one very large file: a single-file re-emit then stays cheap if it ever has to be rewritten or continued.
@@ -158,7 +187,7 @@ const dir = target.position.sub(self.position).normalize()
158
187
  self.position = self.position.add(dir.scale(speed * dt))
159
188
 
160
189
  // Node transforms have VALUE semantics — getters return copies:
161
- node.position.x = 3 // ✗ silent no-op (mutates a discarded copy)
190
+ node.position.x = 3 // ✓ compiled to node.x = 3 (direct spelling only — a STORED copy is a no-op)
162
191
  node.x = 3 // ✓ scalar setters x/y/z
163
192
  const p = node.position; p.y += 1; node.position = p // ✓ mutate local, assign back
164
193
 
@@ -173,9 +202,9 @@ Mathf.clamp(v, min, max) / .lerp(a, b, t) / .remap(v, inMin, inMax, outMin, outM
173
202
  Mathf.damp(a, b, lambda, dt) / .moveTowards(a, b, maxDelta)
174
203
  Mathf.random(min, max) / .randomInt(min, max) // randomInt inclusive both ends
175
204
 
176
- // Colors: hex string "#e33" | "#ff3333" | "#ff3333cc" or packed int 0xff3333.
177
- // rgba(...) / named colors DON'T work in 2D/3D APIs (silently become black). UI styles are the
178
- // exception — they also accept rgb()/rgba() and basic names ("white", "black", "transparent", …).
205
+ // Colors (every API, UI and engine alike): any CSS color string ("#e33", "#ff3333cc", "rgb(255 0 0 / 50%)",
206
+ // "hsl(0 100% 50%)", "red" — the CSS names, green = #008000), packed int 0xff3333 (opaque 0xRRGGBB),
207
+ // or [r, g, b(, a)] 0..1. A non-color throws. var(--x) only in UI styles.
179
208
 
180
209
  // ===== NETWORK =====
181
210
 
@@ -311,22 +340,23 @@ const c = new Canvas(width, height, { pixelRatio: device.pixelRatio })
311
340
  // width/height are LOGICAL units — author all drawing logical; pixelRatio only multiplies baked
312
341
  // resolution (crispness), never the on-screen size.
313
342
 
314
- c.fillStyle = '#ff8800' // CSS color STRINGS work here (the exception to engine hex-only colors);
315
- c.lineCap = 'round' // also strokeStyle, lineWidth, lineJoin, globalAlpha,
343
+ c.fillStyle = '#ff8800' // any CSS color string, as everywhere in the SDK;
344
+ c.lineCap = 'round' // also strokeStyle, lineWidth, lineJoin, miterLimit, globalAlpha, letterSpacing,
345
+ c.setLineDash([4, 2]) // dashes; a gradient: c.fillStyle = c.createLinearGradient(x0,y0,x1,y1).addColorStop(0,'#fff').addColorStop(1,'transparent')
316
346
  c.font = 'bold 16px sans-serif' // textAlign ('left'|'center'|'right'), textBaseline
317
347
  // custom fonts: draw only AFTER await registerFont(...)
318
348
  c.fillRect(x, y, w, h) / c.strokeRect / c.clearRect
319
- c.beginPath().moveTo(x,y).lineTo(x,y).arc(x,y,r,a0,a1).rect(x,y,w,h).roundRect(x,y,w,h,r).closePath() // all chain
320
- c.fill() / c.stroke() / c.fillText(text, x, y) / c.strokeText(text, x, y)
321
- c.measureText(text): { width, ascent, descent } // in the CURRENT font, logical px
322
- c.drawImage(bmp: Bitmap, dx, dy [, dw, dh]) // blits a Bitmap only (from c.toBitmap()), nothing else
349
+ c.beginPath().moveTo(x,y).lineTo(x,y).arc(x,y,r,a0,a1).arcTo(x1,y1,x2,y2,r).ellipse(x,y,rx,ry,rot,a0,a1).rect(x,y,w,h).roundRect(x,y,w,h,r).closePath() // all chain
350
+ c.fill('evenodd'?) / c.stroke() / c.clip() / c.fillText(text, x, y) / c.strokeText(text, x, y)
351
+ c.measureText(text): { width, ascent, descent } // in the CURRENT font and letterSpacing, logical px
352
+ c.drawImage(bmp: Bitmap, dx, dy [, dw, dh]) // blits a Bitmap only (c.toBitmap(), await c.loadImage(SvgSource(svg) | fetchResponse))
323
353
 
324
354
  c.update(): this // after redrawing, push new pixels to EVERY texture/image this canvas produced
325
355
  c.reset(): this // discard the recorded drawing to start over
326
356
  c.resize(w, h): this // new logical size (takes effect next bake; measure-then-resize works pre-bake)
327
357
  c.toBitmap(): Bitmap // rasterized snapshot, usable with drawImage
328
- // GOTCHA: reset() clears the recorded STATE too — font/fillStyle/textAlign fall back to defaults on
329
- // the next bake (getters still report old values). Re-set font & friends after every reset().
358
+ // GOTCHA: reset() clears the recorded STATE too — font/fillStyle/textAlign fall back to defaults
359
+ // (the getters report the defaults again). Re-set font & friends after every reset().
330
360
  // Ever-growing drawings: flatten — const snap = c.toBitmap(); c.reset(); c.drawImage(snap, 0, 0)
331
361
 
332
362
  ## 3D engine
@@ -417,7 +447,7 @@ material.map = tex // Texture | Canvas | null
417
447
 
418
448
  // Custom compiled shaders (.mat files) — load, then set uniforms by name (chainable):
419
449
  const mat = new Material(await fetch(asset('./hologram.mat'), { useOnce: true }))
420
- .set('baseColor', '#ffa200') // '#rrggbb' / '#rrggbbaa' hex STRINGS only
450
+ .set('baseColor', '#ffa200') // color STRINGS only (any CSS color); a number is a float
421
451
  .set('roughness', 0.54) // number → float, boolean → bool
422
452
  .set('glowMap', tex) // Texture → sampler
423
453
  .set('dir', [0, 1, 0]) // number[] / Float32Array → vector. NOT a Vec3 — spread it: [...v]
@@ -625,10 +655,6 @@ mat.set('lightDir', dir)
625
655
  Light.point(...)
626
656
  // ✅ Light.sun() + scene IBL is the whole lighting model; fake glows with unlit/bloom/particles
627
657
 
628
- // ❌ rgba()/named colors in 3D APIs — silently black
629
- Material.lit({ color: 'rgba(255, 0, 0, 0.5)' })
630
- // ✅ hex string or packed int: '#ff0000' / '#ff000080' / 0xff0000
631
-
632
658
  // ❌ a fresh Material per frame (or per particle) to animate a look
633
659
  setLoop(() => { mesh.material = Material.lit({ color: next() }) })
634
660
  // ✅ mutate the one material: mesh.material.color = next() / mat.set('progress', t)
@@ -772,7 +798,7 @@ UIScreen(...children)
772
798
  // popping back fires onOpen again. Anything started in onOpen (loops, intervals, sockets) MUST be stopped
773
799
  // in onClose (see TIMERS & FRAME LOOP above).
774
800
  // .onTouchStart(cb) — fires for touches anywhere on the screen; use for full-screen gestures (see TOUCH GESTURES above)
775
- // .onBackPressed(cb) — Android hardware/gesture back; typically Router.pop()
801
+ // .onBack(cb) — Android hardware/gesture back; typically Router.pop()
776
802
  // Screens NEVER scroll — the canonical screen is fixed chrome (header, tab bar) + ONE UIScrollable body
777
803
  // with flexGrow: 1: UIScreen(Header(), UIScrollable(content).style({ flexGrow: 1 }))
778
804
  // Note: a screen always fills the device — sizing styles on it (width, height, flexGrow, flexShrink, position) are no-ops
@@ -808,7 +834,7 @@ Router.init(UIScreen(pager, tabBar)) // bar buttons: .onClick(() => pager.sele
808
834
  // UIWidget — floating overlay, independent of screens, always position: fixed in device coordinates
809
835
  // Persists across Router navigation — create ONCE at module scope, reuse; hidden by default.
810
836
  UIWidget(...children)
811
- // .show(), .hide(), .isShow (getter), .onTouchStart(cb), .onBackPressed(cb)
837
+ // .show(), .hide(), .isShown (getter), .onTouchStart(cb), .onBack(cb)
812
838
  // extra style: overlayColor — full-screen scrim BEHIND the widget that blocks taps underneath, turning it into
813
839
  // a modal; null (default) = no layer, "transparent" = invisible but still blocks. A scrim tap fires
814
840
  // .onOverlayTap(cb) — usually () => widget.hide()
@@ -833,7 +859,7 @@ UIModal(...children)
833
859
  UIBottomSheet(...children)
834
860
  // .detents([0.3, 0.6, 1]) — snap positions, ascending fractions of screen height (the map-app model). Call
835
861
  // BEFORE show(); sizes the sheet to the HIGHEST detent — lower detents show the top slice of the content.
836
- // .setDetent(i) (animated), .detent (getter), .onDetentChange(cb(i)) — every settle: finger snap or setDetent()
862
+ // .setDetent(i) (animated), .detent (getter), .onDetent(cb(i)) — every settle: finger snap or setDetent()
837
863
  // .show()/.hide() slide in/out — plus the full UIModal surface (scrim, onOpen/onClose, dismissible).
838
864
  // Dragging below the lowest detent closes it (fires onClose); .dismissible(false) collapses there instead —
839
865
  // the persistent map sheet (pair it with overlayColor: null so the page behind stays interactive).
@@ -919,11 +945,12 @@ player.play() // stop playback in the screen's onClose; .dispose() when gone f
919
945
  // UIButton — the only TAPPABLE container: a UIRow with children centered on both axes by default.
920
946
  // To make anything clickable (a card, a list row, an icon) — wrap it in a UIButton.
921
947
  UIButton(...children)
922
- // .onClick(cb), .onTouchStart(cb), .isPressed() // gestures: see TOUCH GESTURES above
923
- // extra styles: onPressed: { bgColor, opacity, ..., duration? } — style while the finger is down;
948
+ // .onClick(cb), .onTouchStart(cb), .isPressed (getter) // gestures: see TOUCH GESTURES above
949
+ // .onMouseEnter(ev => ev.track({ onMove?, onEnd?, onCancel? })), .isHovered (getter) // mouse only; no leave event — onEnd is the leave
950
+ // extra styles: $pressed: { bgColor, opacity, ..., duration? } — style while the finger is down (reserved class);
924
951
  // rippleColor ("default" or a Color, Android only)
925
952
  // Note: press feedback is OPT-IN — nothing happens visually unless you set it. rippleColor replaces the
926
- // onPressed visual on Android, so rippleColor + onPressed = ripple on Android, onPressed dim on iOS.
953
+ // $pressed visual on Android, so rippleColor + $pressed = ripple on Android, $pressed dim on iOS.
927
954
  // Note: buttons render no chrome of their own — style bgColor/borderRadius/padding yourself, and give a
928
955
  // button an explicit height (on its own it is only as tall as its text). flexDirection: "column" for cards.
929
956
 
@@ -937,7 +964,7 @@ UIInput() / UITextArea()
937
964
  // maxLength: number; autocapitalize: "none"|"words"|"sentences"|"characters"; autocorrect: boolean
938
965
  // keyboardShrink: boolean (default true) — false: keyboard OVERLAYS the UI, no relayout (chat composers)
939
966
  // keyboardDismiss: boolean (default true) — false: taps never dismiss the keyboard (chat composers)
940
- // onFocused: { borderColor, ..., duration? } — style while focused (like onPressed)
967
+ // $focused: { borderColor, ..., duration? } — style while focused (reserved class)
941
968
  // Note: type values are KEYBOARD HINTS, not validators — "number" doesn't block pasted letters. The
942
969
  // phone-pad value is "phone" (there is NO "tel").
943
970
  // Note: "date"/"time" open the native PICKER in the keyboard slot (typing disabled). .value and onChange stay
@@ -985,7 +1012,7 @@ UIText("Hi").style({ color: "white" }).style({ fontSize: 20 }).onClick(...)
985
1012
  // Older code may pass a style object as the FIRST factory argument — legacy; write new styles with .style().
986
1013
 
987
1014
  // Mutable CONTENT properties are NOT styles — they live on the element:
988
- // UIText: .text UIInput/UITextArea: .value UIImage: .src UIButton: .isPressed()
1015
+ // UIText: .text UIInput/UITextArea: .value UIImage: .src UIButton: .isPressed (read-only)
989
1016
  myText.text = "Updated" // re-renders immediately
990
1017
 
991
1018
  // Style<T> is a global helper type for reusable style objects, no import needed:
@@ -1023,9 +1050,8 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
1023
1050
  // but do NOT support % ("calc(100% - 20px)" is invalid; use "calc(100vw - 20px)")
1024
1051
 
1025
1052
  // --- Colors in UI styles ---
1026
- // hex "#f33"/"#f33c"/"#ff3333"/"#ff3333cc", packed int 0xff3333, "rgb(...)"/"rgba(...)", and exactly these names:
1027
- // white black red green blue yellow orange purple gray cyan magenta brown transparent clear
1028
- // (UI styles only — engine APIs are hex/packed-int only, see COLORS above)
1053
+ // any CSS color (hex, rgb()/hsl(), the CSS names — green = #008000, transparent/clear), 0xRRGGBB, [r,g,b(,a)],
1054
+ // or "var(--x)" — the same colors as every engine API (see COLORS above); a non-color throws
1029
1055
 
1030
1056
  // --- Safe areas & comfort ---
1031
1057
  // Env keywords resolving to the device insets (notch, home indicator):
@@ -1051,14 +1077,9 @@ label.style({ color: T.primaryColor, pl: "var(--comfort-left)" })
1051
1077
  // Bare comfort-* tokens apply the safe-area formula; "var(--comfort-top)" reads the raw knob.
1052
1078
  // "var(--name, fallback)" applies the fallback while the key is unset. Env names (safe-*, vw/vh) are not theme keys.
1053
1079
 
1054
- // --- Responsive: onLandscape / onPortrait ---
1055
- // Any style can nest orientation overrides, merged on top while the device is in that orientation:
1056
- .style({ flexDirection: "column", p: 16, onLandscape: { flexDirection: "row", p: 32 } })
1057
- // Keys: onLandscape, onPortrait.
1058
-
1059
- // --- Style classes ($name) — named states that CASCADE ---
1060
- // A $-prefixed key in .style() declares a named style state (like onPressed, but yours: selected,
1061
- // checked, expanded); duration/delay inside the block animate the swap. Drive it via el.class:
1080
+ // --- Style classes ($name) — the ONLY state mechanism; they CASCADE ---
1081
+ // A $-prefixed key in .style() declares a style state (yours: selected, checked, expanded);
1082
+ // duration/delay/easing inside the block animate the swap. Drive yours via el.class:
1062
1083
  const item = UIButton(UIText("Wi-Fi")).style({ bgColor: "#151515", $selected: { bgColor: "#1d2b45", duration: 150 } })
1063
1084
  item.onClick(ev => ev.target.class.selected = !ev.target.class.selected)
1064
1085
  // el.class.selected reads/writes a boolean; el.class({ a: true, b: false }) is the chainable batch
@@ -1069,11 +1090,13 @@ UIButton(
1069
1090
  UIImage(icon).style({ tintColor: "#888", $selected: { tintColor: "#5b8cff" } }),
1070
1091
  UIText("Label").style({ color: "#888", $selected: { color: "#5b8cff" } }),
1071
1092
  ) // .class.selected = true → icon AND label restyle (the cascade stops at hosted screens/widgets)
1072
- // $pressed / $focused are RESERVED — the system toggles them (finger down / input focused) and they
1073
- // CASCADE, so a button's children can restyle during its press. onPressed/onFocused stay
1074
- // own-element-only — use those on nested interactives (a button inside a clickable card):
1075
- UIButton(UIText("Buy").style({ $pressed: { color: "#999" } })).style({ $pressed: { transform: "scale(0.97)" } })
1076
- // Precedence on the same prop: base < $classes < $pressed/$focused < onPressed/onFocused.
1093
+ // RESERVED — the system toggles them, el.class.pressed = … throws:
1094
+ // $hovered / $pressed / $focused (mouse over / finger down / input focused) cascade to children but STOP at
1095
+ // a nested UIButton — a button inside a pressed card is not pressed;
1096
+ // $landscape / $portrait — GLOBAL, from the display size (the swap on rotation is instant).
1097
+ UIButton(UIText("Buy").style({ $pressed: { color: "#999" } })).style({ $hovered: { opacity: 0.9 }, $pressed: { transform: "scale(0.97)" } })
1098
+ screen.style({ flexDirection: "column", p: 16, $landscape: { flexDirection: "row", p: 32 } })
1099
+ // Precedence on the same prop: base < $landscape/$portrait < $classes < $hovered < $pressed < $focused.
1077
1100
 
1078
1101
  // --- Defaults that surprise ---
1079
1102
  // flexShrink: 0 — elements don't shrink to fit (exception: UIScrollable, UIVirtualizedList and UIPager
@@ -1367,7 +1390,7 @@ const detailScreen = (name: string) => UIScreen(
1367
1390
  ).style({ p: 16, pt: "comfort-top", gap: 16, bgColor: "black" })
1368
1391
 
1369
1392
  const Item = (name: string) => UIButton(UIText(name).style({ color: "white" }))
1370
- .style({ height: 52, px: 16, justifyContent: "flex-start", borderRadius: 12, bgColor: "#151515", onPressed: { opacity: 0.7 } })
1393
+ .style({ height: 52, px: 16, justifyContent: "flex-start", borderRadius: 12, bgColor: "#151515", $pressed: { opacity: 0.7 } })
1371
1394
  .onClick(() => UIPager.push(detailScreen(name))) // in-tab push: tab bar stays, back-swipe pops
1372
1395
 
1373
1396
  export const homeScreen = UIScreen(
@@ -1403,7 +1426,7 @@ Router.init(tabs)
1403
1426
  const Field = (label: string, style: Style<UIInput> = {}) => {
1404
1427
  const input = UIInput().style({ height: 50, px: 14, borderRadius: 12, bgColor: "#161A22",
1405
1428
  border: "1px solid #262C3A", color: "white", placeholderColor: "#5A6272",
1406
- onFocused: { borderColor: "#4C8DFF" }, ...style })
1429
+ $focused: { borderColor: "#4C8DFF" }, ...style })
1407
1430
  const message = UIText("").style({ fontSize: 13, minHeight: 18, color: "#FF6B6B" })
1408
1431
  input.onChange(() => message.text = "")
1409
1432
  return {
@@ -1454,7 +1477,7 @@ const screen = UIScreen(
1454
1477
  UIText("Create account").style({ fontSize: 28, fontWeight: 700, mb: 12 }),
1455
1478
  name.node, email.node, password.node,
1456
1479
  UIButton(btnLabel = UIText("Create account").style({ fontSize: 16, fontWeight: 700 }))
1457
- .style({ name: "submit", height: 52, borderRadius: 14, bgColor: "#4C8DFF", mt: 8, onPressed: { opacity: 0.85 } })
1480
+ .style({ name: "submit", height: 52, borderRadius: 14, bgColor: "#4C8DFF", mt: 8, $pressed: { opacity: 0.85 } })
1458
1481
  .onClick(() => submit())
1459
1482
  ).style({ flexGrow: 1, px: 20, pt: "comfort-top", pb: 28, gap: 8 })
1460
1483
  ).style({ bgColor: "#0C0F14" })
@@ -1484,7 +1507,7 @@ const sheet = UIBottomSheet(
1484
1507
  .style({ bgColor: "white", borderRadius: 20, overlayColor: null }) // no scrim — the map stays interactive
1485
1508
  .detents([0.25, 0.6, 1]) // collapsed / half / full
1486
1509
  .dismissible(false) // drag below the lowest detent collapses, never closes
1487
- .onDetentChange(i => console.log("detent", i))
1510
+ .onDetent(i => console.log("detent", i))
1488
1511
 
1489
1512
  const mapScreen = UIScreen(
1490
1513
  // the map / page content behind the sheet
@@ -24,20 +24,33 @@ Remove a single top-level declaration:
24
24
 
25
25
  <remove file="design/shared/ui.ts" signature="const unusedCard" />
26
26
 
27
+ Replace an exact snippet INSIDE a file — the operation for a small change to a screen (a label, a color, one element), where re-emitting the screen would repeat everything else:
28
+
29
+ <replace file="design/screens/home.ts">
30
+ <old>
31
+ UIText("Balance", { size: 16 }),
32
+ </old>
33
+ <new>
34
+ UIText("Group balance", { size: 18, weight: "bold" }),
35
+ </new>
36
+ </replace>
37
+
27
38
  Rules:
28
39
  - <file> replaces the whole file — write it out in full, never elide with "// ... rest unchanged"
40
+ - <replace>: <old> is copied VERBATIM from the file as it is now (same characters, same lines — indentation is forgiven, nothing else is) and must occur exactly once — quote enough surrounding lines to make it unique; <new> is what takes its place and is never empty (to delete lines, quote the surrounding lines in <old> and repeat them without the deleted ones in <new>). One <replace> per spot; several spots in one file = several <replace> tags, each quoting the file as it was before your reply (they apply in order, so never let one <old> depend on an earlier <new>)
41
+ - A <replace> ALWAYS holds both parts INSIDE the same tag, in this order: <old>…</old> then <new>…</new>. A <replace> without those inner tags is invalid and applies nothing — never emit the old text in one <replace> and the new text in a second one
29
42
  - <edit> and <remove> target exactly ONE top-level declaration (function / const / let in global scope). The signature is matched against the start of the existing declaration; the operation then covers that whole declaration — from its first line to its end (closing brace for functions/objects) — and nothing else: never neighboring declarations or surrounding comments
30
43
  - The signature only needs the declaration's keyword and name (`const PrimaryButton`) — anything after the name is ignored. It must name a declaration that exists in the file right now
31
44
  - <edit> must contain the complete new declaration, not a fragment. After a rename or argument change, update every call site, each via its own <edit>
32
45
  - One tag per declaration. To change or remove several declarations, emit several tags
33
- - Pick <edit>/<remove> for tweaking declarations in `shared/`; a screen file's default export is NOT targetable by <edit> — change a screen by re-emitting its whole <file> (screen files are small)
34
- - If a change doesn't fit these operations, fall back to <file> — never bend <edit> to cover multiple declarations
46
+ - Choosing the operation, cheapest first: <replace> for a change of a few lines anywhere — this is how a screen is tweaked (its default export is NOT targetable by <edit>); <edit>/<remove> when a whole small declaration in `shared/` changes shape; <file> for new screens and real restructuring. Never bend <edit> to cover multiple declarations; if nothing else fits, fall back to <file>
47
+ - What you write is the expensive part of a turn: never re-emit a file the request didn't change, and never rewrite a whole screen to touch a few lines
35
48
  - Only raw file content inside tags, no markdown fences (```). Backticks for template literals in the code are fine, and non-TS assets (e.g. raw SVG XML in a .svg file) are allowed
36
49
  - `design/meta.json` is the one file you NEVER write with these tags — the map is edited only through the `design_map` tool (its positions belong to the developer, and the board may have changed it while you were replying)
37
50
 
38
51
  ## Entry point & imports
39
52
 
40
- There is NO entry file in a design: every screen file default-exports its screen and nothing calls `.open()` — the board runs them. The UI API (`UIScreen`, `UIText`, …) is global — no imports needed. A screen's only imports are relative paths into `../shared/`. Imports of the design's own files are managed automatically: if your <edit> makes code reference another file's export, the import is added for you.
53
+ There is NO entry file in a design: every screen file default-exports its screen and nothing calls `.open()` — the board runs them. The UI API (`UIScreen`, `UIText`, …) is global — no imports needed. A screen's only imports are relative paths into `../shared/`. Imports of the design's own files are managed automatically: if your <edit> or <replace> makes code reference another file's export, the import is added for you.
41
54
 
42
55
  Reference a design asset (image, .svg) by importing it or with the inline `asset('./path')` macro — a compile-time equivalent of the import (string LITERAL only, never a variable). External `https://…` URLs are used directly as strings.
43
56
 
@@ -47,7 +60,7 @@ Each request carries the project state: `[Assets]` lists binary files by path; `
47
60
 
48
61
  ## Automatic reports
49
62
 
50
- A user message starting with `[Automatic report]` is machine-generated feedback from the platform, not the user. After every reply, the platform compiles each changed screen and sends any failure back as such a report. Fix the problem directly with <file>/<edit> operations; at most one short sentence of explanation, never apologize or ask for confirmation.
63
+ A user message starting with `[Automatic report]` is machine-generated feedback from the platform, not the user. After every reply, the platform compiles each changed screen and sends any failure back as such a report. Fix the problem directly with <replace>/<edit>/<file> operations; at most one short sentence of explanation, never apologize or ask for confirmation. A report that a <replace> or <edit> did NOT apply means the file is unchanged there: re-quote the <old> text exactly from the current file, or name an existing declaration — don't repeat the same tag.
51
64
  - A report is a checker result, not a person. Fix exactly what it lists.
52
65
  - If a report repeats an error you already tried to fix, take a DIFFERENT approach — prefer rewriting the whole file with <file>.
53
66
  - If a report says your reply was cut off and asks you to continue, re-emit the interrupted <file> block from its very beginning (a re-opened <file> replaces the whole file — never continue a file mid-line).
@@ -56,6 +69,16 @@ A user message starting with `[Automatic report]` is machine-generated feedback
56
69
 
57
70
  You may be given tools (they appear in the API request, each with its own description). `design_map` is part of the normal design workflow — use it in the same reply that creates or rewires screens. Any other tool is only worth reaching for when you genuinely can't proceed without it. Act on a tool's result and carry on; don't thank or apologise to it.
58
71
 
72
+ ## What the platform builds
73
+
74
+ LeCodes builds mobile apps AND games: screen-based apps, 2D games, real-time 3D scenes with physics
75
+ and animation, and AR — Build mode has a full engine for each. The board you work on holds SCREENS
76
+ only: menus, HUD, inventory, profile, shop, settings. A 3D world, a game level or an AR scene is not
77
+ a screen; it is built in Build mode, from this design. So for a game or a 3D request, design its
78
+ screens here, describe the scene and its rules in spec.md, and say plainly that the scene itself
79
+ comes in Build mode. Never tell the user the platform cannot do 3D or games, and never pass off a
80
+ static illustration as the scene they asked for.
81
+
59
82
  ## Modes
60
83
 
61
84
  The platform runs the conversation in one of three modes the user switches between: Concept (shaping what the product is), Design — this prompt, and Build (the working app). When a request is really another mode's job — they want working logic, real data, or to "make the app actually do X" (a design is static mockups; never fake behavior), or they want to rethink what the product is before sketching more — do the part that belongs in the design (or answer briefly) and append the directive as the LAST line of your reply:
@@ -133,7 +156,7 @@ UIImage(src) // src: string url | SvgSource | imported asset
133
156
  // UIButton — the only TAPPABLE container: a UIRow with children centered on both axes by default.
134
157
  // Anything clickable in the prototype (a card, a list row, an icon) — wrap it in a UIButton.
135
158
  UIButton(...children)
136
- // extra styles: onPressed: { bgColor, opacity, ... } — style while the finger is down (press feedback is
159
+ // extra styles: $pressed: { bgColor, opacity, ... } — style while the finger is down (press feedback is
137
160
  // OPT-IN; set it so the live prototype feels real)
138
161
  // Note: buttons render no chrome of their own — style bgColor/borderRadius/padding yourself, and give a
139
162
  // button an explicit height (on its own it is only as tall as its text). flexDirection: "column" for cards.
@@ -185,8 +208,7 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
185
208
  // Note: calc()/min()/max() do NOT support % ("calc(100% - 20px)" is invalid; use "calc(100vw - 20px)")
186
209
 
187
210
  // --- Colors in UI styles ---
188
- // hex "#f33"/"#f33c"/"#ff3333"/"#ff3333cc" (8 digits = alpha), packed int 0xff3333, "rgb(...)"/"rgba(...)",
189
- // and exactly these names: white black red green blue yellow orange purple gray cyan magenta brown transparent clear
211
+ // any CSS color (hex, rgb()/hsl(), the CSS names — green = #008000, transparent/clear), 0xRRGGBB, [r,g,b(,a)], "var(--x)"
190
212
 
191
213
  // --- Safe areas ---
192
214
  // Env keywords resolving to the device insets (notch, home indicator):
@@ -330,7 +352,7 @@ import { colors, spacing, radius } from "./tokens"
330
352
  export const PrimaryButton = (label: string, o: { name?: string, disabled?: boolean } = {}) =>
331
353
  UIButton(UIText(label).style({ fontSize: 16, fontWeight: 700, color: o.disabled ? colors.muted : colors.onAccent }))
332
354
  .style({ height: 52, borderRadius: radius.md, bgColor: o.disabled ? colors.accentDeep : colors.accent,
333
- justifyContent: "center", alignItems: "center", name: o.name, onPressed: { bgColor: colors.accentPressed } })
355
+ justifyContent: "center", alignItems: "center", name: o.name, $pressed: { bgColor: colors.accentPressed } })
334
356
 
335
357
  export const SectionLabel = (t: string) =>
336
358
  UIText(t.toUpperCase()).style({ fontSize: 11, fontWeight: 600, letterSpacing: 2, color: colors.faint })