lecodes-sdk 1.2.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) 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 +8 -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/Scene.d.ts +41 -13
  25. package/dist/types/gl/Texture.d.ts +1 -1
  26. package/dist/types/gl/animation/Locomotion.d.ts +8 -1
  27. package/dist/types/inject.d.ts +11 -11
  28. package/dist/types/inject.editor.d.ts +1 -0
  29. package/dist/types/net/core.d.ts +7 -0
  30. package/dist/types/plugin.d.ts +76 -0
  31. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  32. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  33. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  34. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  35. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  36. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  37. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  38. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  39. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  40. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  41. package/dist/types/runtime/app.d.ts +9 -2
  42. package/dist/types/runtime/fetch.d.ts +2 -0
  43. package/dist/types/runtime/input.d.ts +1 -1
  44. package/dist/types/runtime/media.d.ts +6 -10
  45. package/dist/types/runtime/misc.d.ts +4 -1
  46. package/dist/types/runtime/net.d.ts +3 -2
  47. package/dist/types/runtime/touch.d.ts +32 -0
  48. package/dist/types/scene/defineScene.d.ts +43 -2
  49. package/dist/types/scene/editor.d.ts +52 -0
  50. package/dist/types/scene/gizmos.d.ts +7 -4
  51. package/dist/types/ui/NativeView.d.ts +6 -4
  52. package/dist/types/ui/UI.d.ts +1 -1
  53. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  54. package/dist/types/ui/UIButton.d.ts +12 -14
  55. package/dist/types/ui/UIContainer.d.ts +0 -6
  56. package/dist/types/ui/UIImage.d.ts +1 -4
  57. package/dist/types/ui/UIInput.d.ts +8 -24
  58. package/dist/types/ui/UIModal.d.ts +0 -2
  59. package/dist/types/ui/UINode.d.ts +70 -56
  60. package/dist/types/ui/UIPager.d.ts +28 -27
  61. package/dist/types/ui/UIPopover.d.ts +0 -2
  62. package/dist/types/ui/UIScreen.d.ts +12 -17
  63. package/dist/types/ui/UIScrollable.d.ts +1 -4
  64. package/dist/types/ui/UIText.d.ts +0 -2
  65. package/dist/types/ui/UIVideo.d.ts +3 -5
  66. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  67. package/dist/types/ui/UIWidget.d.ts +10 -9
  68. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  69. package/dist/types/ui/presentable.d.ts +46 -32
  70. package/dist/types/ui/router.d.ts +18 -7
  71. package/dist/types/ui/styleColor.d.ts +1 -0
  72. package/dist/types/ui/transitions.d.ts +18 -0
  73. package/dist/types/ui/tree.d.ts +75 -0
  74. package/dist/types/version.d.ts +10 -0
  75. package/dist/types.json +1 -1
  76. package/package.json +12 -3
  77. package/prompts/2d.md +2 -6
  78. package/prompts/3d.md +1 -5
  79. package/prompts/README.md +1 -1
  80. package/prompts/canvas.md +9 -8
  81. package/prompts/compose.ts +1 -1
  82. package/prompts/core.md +3 -3
  83. package/prompts/design.md +1 -1
  84. package/prompts/dist/2d-game.md +473 -239
  85. package/prompts/dist/3d-app.md +553 -205
  86. package/prompts/dist/ar-app.md +435 -202
  87. package/prompts/dist/design.md +113 -95
  88. package/prompts/dist/ui-app.md +386 -170
  89. package/prompts/ui-design.md +2 -3
  90. package/prompts/ui.md +45 -37
  91. package/src/animate/tween/Animation.ts +34 -150
  92. package/src/animate/tween/Timeline.ts +175 -175
  93. package/src/animate/tween/animateValue.ts +6 -3
  94. package/src/animate/tween/easing.ts +10 -3
  95. package/src/animate/tween/spec.ts +41 -15
  96. package/src/audio/Sound.ts +3 -3
  97. package/src/audio/audio.ts +2 -1
  98. package/src/bridges/2d.d.ts +317 -0
  99. package/src/bridges/app.d.ts +91 -0
  100. package/src/bridges/audio.d.ts +97 -0
  101. package/src/bridges/canvas.d.ts +79 -0
  102. package/src/bridges/device.d.ts +72 -0
  103. package/src/bridges/fetch.d.ts +80 -0
  104. package/src/bridges/files.d.ts +70 -0
  105. package/src/bridges/gl.d.ts +1133 -0
  106. package/src/bridges/input.d.ts +72 -0
  107. package/src/bridges/media.d.ts +55 -0
  108. package/src/bridges/nav.d.ts +71 -0
  109. package/src/bridges/net.d.ts +52 -0
  110. package/src/bridges/service.d.ts +52 -0
  111. package/src/bridges/socket.d.ts +31 -0
  112. package/src/bridges/storage.d.ts +33 -0
  113. package/src/bridges/tree.d.ts +301 -0
  114. package/src/bridges/types.d.ts +49 -0
  115. package/src/canvas/Canvas.ts +114 -159
  116. package/src/canvas/gen/cssColor.ts +224 -0
  117. package/src/canvas/gen/recorder.ts +212 -0
  118. package/src/canvas/gen/spec.ts +201 -0
  119. package/src/chisel.ts +193 -0
  120. package/src/compile/assetMacro.ts +1 -1
  121. package/src/compile/bundler.ts +11 -2
  122. package/src/compile/compileProject.ts +43 -4
  123. package/src/compile/fontMacro.ts +3 -4
  124. package/src/compile/header.ts +26 -5
  125. package/src/compile/index.ts +3 -1
  126. package/src/compile/liteMaterial.ts +1 -1
  127. package/src/compile/sceneEditor.ts +11 -26
  128. package/src/core/color.ts +73 -30
  129. package/src/core/pins.ts +51 -0
  130. package/src/core/signals.ts +8 -1
  131. package/src/g2/CharacterController2D.ts +3 -3
  132. package/src/g2/Node2D.ts +57 -39
  133. package/src/g2/Physics2D.ts +2 -2
  134. package/src/g2/Scene2D.ts +35 -23
  135. package/src/g2/Texture2D.ts +1 -1
  136. package/src/g2/loop.ts +4 -4
  137. package/src/gl/CameraPlace.ts +52 -52
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +10 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Mesh.ts +120 -120
  144. package/src/gl/Model.ts +21 -16
  145. package/src/gl/Node.ts +91 -24
  146. package/src/gl/Particles.ts +1 -1
  147. package/src/gl/Scene.ts +100 -44
  148. package/src/gl/Texture.ts +8 -7
  149. package/src/gl/animation/AnimationClip.ts +1 -1
  150. package/src/gl/animation/DynamicBone.ts +482 -482
  151. package/src/gl/animation/Locomotion.ts +8 -3
  152. package/src/gl/nav/NavMesh.ts +3 -4
  153. package/src/gl/physics/Physics.ts +2 -2
  154. package/src/gl/physics/physicsEvents.ts +3 -3
  155. package/src/gl/scenarios.ts +291 -291
  156. package/src/gl/terrain/Terrain.ts +4 -5
  157. package/src/gl/touch.ts +14 -15
  158. package/src/host.d.ts +2 -3
  159. package/src/inject.editor.ts +7 -0
  160. package/src/inject.ts +13 -16
  161. package/src/net/core.ts +6 -5
  162. package/src/net/index.ts +1 -1
  163. package/src/net/replication.ts +1 -1
  164. package/src/plugin.ts +191 -0
  165. package/src/plugins/gen/camera/contract.d.ts +27 -0
  166. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  167. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  168. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  169. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  170. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  171. package/src/plugins/gen/map/contract.d.ts +144 -0
  172. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  173. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  174. package/src/plugins/gen/push/contract.d.ts +61 -0
  175. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  176. package/src/plugins/gen/push/sdk/push.ts +105 -0
  177. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  178. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  179. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  180. package/src/plugins/permission.ts +5 -4
  181. package/src/runtime/app.ts +20 -9
  182. package/src/runtime/appEvents.ts +5 -4
  183. package/src/runtime/channel.ts +18 -15
  184. package/src/runtime/clipboard.ts +4 -3
  185. package/src/runtime/datetime.ts +2 -1
  186. package/src/runtime/device.ts +17 -15
  187. package/src/runtime/fetch.ts +30 -20
  188. package/src/runtime/files.ts +16 -15
  189. package/src/runtime/input.ts +12 -10
  190. package/src/runtime/media.ts +50 -46
  191. package/src/runtime/misc.ts +7 -3
  192. package/src/runtime/net.ts +8 -7
  193. package/src/runtime/rpc.ts +1 -3
  194. package/src/runtime/service.ts +19 -14
  195. package/src/runtime/share.ts +4 -3
  196. package/src/runtime/storage.ts +6 -4
  197. package/src/runtime/touch.ts +32 -0
  198. package/src/scene/defineScene.ts +61 -365
  199. package/src/scene/editor.ts +408 -0
  200. package/src/scene/editorPlugins.ts +3 -3
  201. package/src/scene/gizmos.ts +15 -9
  202. package/src/server/db/marci/query.ts +1 -1
  203. package/src/server/host.ts +1 -1
  204. package/src/server/runtime.ts +1 -1
  205. package/src/ui/NativeView.ts +72 -25
  206. package/src/ui/UI.ts +3 -3
  207. package/src/ui/UIBottomSheet.ts +16 -17
  208. package/src/ui/UIButton.ts +54 -16
  209. package/src/ui/UIContainer.ts +0 -6
  210. package/src/ui/UIImage.ts +34 -30
  211. package/src/ui/UIInput.ts +29 -37
  212. package/src/ui/UIModal.ts +1 -3
  213. package/src/ui/UINode.ts +347 -297
  214. package/src/ui/UIPager.ts +93 -78
  215. package/src/ui/UIPopover.ts +0 -2
  216. package/src/ui/UIScreen.ts +41 -36
  217. package/src/ui/UIScrollable.ts +19 -13
  218. package/src/ui/UISpacer.ts +1 -1
  219. package/src/ui/UITabs.ts +8 -6
  220. package/src/ui/UIText.ts +7 -17
  221. package/src/ui/UIVideo.ts +30 -27
  222. package/src/ui/UIVirtualizedList.ts +58 -59
  223. package/src/ui/UIWidget.ts +38 -18
  224. package/src/ui/colorKeys.gen.ts +37 -0
  225. package/src/ui/fonts.ts +2 -2
  226. package/src/ui/presentable.ts +59 -42
  227. package/src/ui/router.ts +52 -35
  228. package/src/ui/styleColor.ts +56 -0
  229. package/src/ui/theme.ts +7 -6
  230. package/src/ui/transitions.ts +249 -0
  231. package/src/ui/tree.ts +346 -0
  232. package/src/version.ts +24 -0
  233. package/tests/helpers/engineWorld.ts +11 -0
  234. package/tests/helpers/fakeTree.ts +353 -0
  235. package/tests/helpers/hostStubs.ts +31 -0
  236. package/tests/helpers/index.ts +14 -0
  237. package/tests/helpers/memoryMarci.ts +124 -0
  238. package/tests/helpers/phases.ts +23 -0
  239. package/tests/helpers/preload.ts +18 -0
  240. package/tests/helpers/stubApp.ts +2 -0
  241. package/tests/helpers/stubDevice.ts +2 -0
  242. package/tests/helpers/stubFetch.ts +2 -0
  243. package/tests/helpers/stubInput.ts +2 -0
  244. package/dist/inject.js +0 -4629
  245. package/dist/types/core/registry.d.ts +0 -7
  246. package/dist/types/plugins/camera.d.ts +0 -25
  247. package/dist/types/plugins/push.d.ts +0 -46
  248. package/src/bridges.d.ts +0 -1769
  249. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  250. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  251. package/src/compile/__tests__/assetName.test.ts +0 -55
  252. package/src/compile/__tests__/compile.test.ts +0 -310
  253. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  254. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  255. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  256. package/src/core/__tests__/stateMachine.test.ts +0 -132
  257. package/src/core/registry.ts +0 -23
  258. package/src/plugins/camera.ts +0 -81
  259. package/src/plugins/push.ts +0 -132
  260. package/src/plugins/qr.ts +0 -73
@@ -1,27 +0,0 @@
1
- import { test, expect } from "bun:test"
2
- import { compileServerBundle } from "../serverSplit"
3
-
4
- // The server bundle's file map is the whole project (a server module may import shared code), and
5
- // the bundler checks every `asset()` in that map. Seen live 2026-09-17: a Figma-exported design next
6
- // to app.server.ts failed the SERVER build with "asset not found: ../assets/figma/…" because the
7
- // map had the screens but not the binaries. Binaries are stubbed, design/ stays out.
8
-
9
- const text = (path: string, t: string) => ({ path, type: "text" as const, text: t })
10
- const res = (path: string) => ({ path, type: "resource" as const, fileSrc: "/uploads/x.bin" })
11
-
12
- test("a client screen's asset() does not break the server bundle", async () => {
13
- const out = await compileServerBundle({
14
- validate: false,
15
- entries: [
16
- text("/app.server.ts", "export const ping = async () => 1\n"),
17
- text("/main.ts", "import { ping } from './app.server'\nconst hero = asset('./hero.png')\nping()\n"),
18
- res("/hero.png"),
19
- text("/design/screens/a4-40.ts", "export default UIScreen([ UIImage(asset('../assets/figma/a4-40/rectangle.png')), UIImage(asset('../assets/figma/a4-40/logo.svg')) ])\n"),
20
- res("/design/assets/figma/a4-40/rectangle.png"),
21
- text("/design/assets/figma/a4-40/logo.svg", "<svg/>"),
22
- ],
23
- })
24
- expect(out).not.toBeNull()
25
- expect(out!.code).toContain("ping")
26
- expect(Object.keys(out!.manifest.modules)).toEqual(["app.server.ts"])
27
- })
@@ -1,132 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { StateMachine, defineStates } from "../StateMachine"
3
-
4
- type Bot = { hp: number; target: boolean; visible: boolean; dist: number; lost: number; fire: boolean; log: string[] }
5
- const makeBot = (): Bot => ({ hp: 100, target: false, visible: false, dist: 99, lost: 0, fire: false, log: [] })
6
-
7
- const Guard = defineStates<Bot>({
8
- initial: "patrol",
9
- on: { hit: "flinch" },
10
- to: { dead: (b) => b.hp <= 0 },
11
- states: {
12
- patrol: { enter: (b) => b.log.push("enter patrol"), exit: (b) => b.log.push("exit patrol"), to: { combat: (b) => b.target } },
13
- combat: {
14
- initial: "engage",
15
- enter: (b) => b.log.push("enter combat"),
16
- exit: (b) => b.log.push("exit combat"),
17
- to: { search: (b) => b.lost > 4 },
18
- states: {
19
- engage: { enter: (b) => b.log.push("enter engage"), exit: (b) => b.log.push("exit engage"), to: { shoot: (b) => b.visible && b.dist < 12 } },
20
- shoot: {
21
- enter: (b) => { b.fire = true; b.log.push("enter shoot") },
22
- exit: (b) => { b.fire = false; b.log.push("exit shoot") },
23
- to: { engage: (b) => !b.visible },
24
- on: { hit: (b) => (b.hp < 30 ? "combat.engage" : undefined) },
25
- },
26
- },
27
- },
28
- search: { after: [6, "patrol"], enter: (b) => b.log.push("enter search") },
29
- flinch: { after: [0.2, "$back"], enter: (b) => b.log.push("enter flinch"), exit: (b) => b.log.push("exit flinch") },
30
- dead: { enter: (b) => b.log.push("enter dead") },
31
- },
32
- })
33
-
34
- const DT = 1 / 60
35
-
36
- describe("StateMachine", () => {
37
- test("enters the initial state and follows guards into nested states", () => {
38
- const bot = makeBot()
39
- const fsm = new StateMachine(Guard, bot)
40
- expect(fsm.state).toBe("patrol")
41
- expect(bot.log).toEqual(["enter patrol"])
42
- fsm.step(DT)
43
- expect(fsm.state).toBe("patrol")
44
- bot.target = true
45
- fsm.step(DT)
46
- expect(fsm.state).toBe("combat.engage")
47
- expect(bot.log.slice(-3)).toEqual(["exit patrol", "enter combat", "enter engage"])
48
- expect(fsm.is("combat")).toBe(true)
49
- expect(fsm.is("engage")).toBe(true)
50
- expect(fsm.is("patrol")).toBe(false)
51
- })
52
-
53
- test("a sibling transition exits and enters only the leaves", () => {
54
- const bot = makeBot()
55
- const fsm = new StateMachine(Guard, bot)
56
- bot.target = true
57
- fsm.step(DT)
58
- bot.visible = true
59
- bot.dist = 5
60
- fsm.step(DT)
61
- expect(fsm.state).toBe("combat.shoot")
62
- expect(bot.fire).toBe(true)
63
- expect(bot.log.slice(-2)).toEqual(["exit engage", "enter shoot"])
64
- })
65
-
66
- test("events: innermost handler wins, the root's `on` is the fallback, $back restores the previous leaf", () => {
67
- const bot = makeBot()
68
- const fsm = new StateMachine(Guard, bot)
69
- bot.target = true; fsm.step(DT)
70
- bot.visible = true; bot.dist = 5; fsm.step(DT)
71
- // handled by shoot (returns undefined → stays), the root's handler is NOT consulted
72
- expect(fsm.send("hit")).toBe(true)
73
- expect(fsm.state).toBe("combat.shoot")
74
- bot.hp = 20
75
- expect(fsm.send("hit")).toBe(true)
76
- expect(fsm.state).toBe("combat.engage")
77
- // engage has no handler → root → flinch, leaving combat
78
- bot.visible = false
79
- fsm.send("hit")
80
- expect(fsm.state).toBe("flinch")
81
- expect(bot.log.slice(-3)).toEqual(["exit engage", "exit combat", "enter flinch"])
82
- for (let i = 0; i < 13; i++) fsm.step(DT)
83
- expect(fsm.state).toBe("combat.engage")
84
- expect(bot.log.slice(-3)).toEqual(["exit flinch", "enter combat", "enter engage"])
85
- expect(fsm.previous).toBe("flinch")
86
- })
87
-
88
- test("compound guards, timed transitions, clocks", () => {
89
- const bot = makeBot()
90
- const fsm = new StateMachine(Guard, bot)
91
- bot.target = true; fsm.step(DT)
92
- bot.lost = 5
93
- fsm.step(DT)
94
- expect(fsm.state).toBe("search")
95
- bot.target = false; bot.lost = 0
96
- for (let i = 0; i < 361; i++) fsm.step(DT)
97
- expect(fsm.state).toBe("patrol")
98
- expect(fsm.time).toBeLessThan(0.05)
99
- })
100
-
101
- test("go() re-enters the current state; guards never re-enter an active state", () => {
102
- const bot = makeBot()
103
- const fsm = new StateMachine(Guard, bot)
104
- bot.log.length = 0
105
- fsm.go("patrol")
106
- expect(bot.log).toEqual(["exit patrol", "enter patrol"])
107
- bot.hp = 0
108
- bot.target = true
109
- const changes: string[] = []
110
- fsm.on("change", (to, from) => changes.push(`${from}>${to}`))
111
- fsm.step(DT) // the any-state `dead` guard beats patrol→combat
112
- expect(fsm.state).toBe("dead")
113
- fsm.step(DT); fsm.step(DT)
114
- expect(changes).toEqual(["patrol>dead"]) // no dead>dead re-entries while the guard stays true
115
- })
116
-
117
- test("unknown targets throw when the machine is built", () => {
118
- expect(() => new StateMachine(defineStates<{}>({ initial: "a", states: { a: { to: { nowhere: () => true } } } }), {})).toThrow(/unknown state 'nowhere'/)
119
- expect(() => new StateMachine(defineStates<{}>({ initial: "zzz", states: { a: {} } }), {})).toThrow()
120
- })
121
-
122
- test("pass-through states chain within one step; update may return a target", () => {
123
- const chain = new StateMachine(defineStates<{}>({ initial: "a", states: { a: { to: { b: () => true } }, b: { to: { c: () => true } }, c: {} } }), {})
124
- chain.step(0)
125
- expect(chain.state).toBe("c")
126
- const upd = new StateMachine(defineStates<{ n: number }>({ initial: "a", states: { a: { update: (c) => (++c.n >= 3 ? "b" : undefined) }, b: {} } }), { n: 0 })
127
- upd.step(0); upd.step(0)
128
- expect(upd.state).toBe("a")
129
- upd.step(0)
130
- expect(upd.state).toBe("b")
131
- })
132
- })
@@ -1,23 +0,0 @@
1
- // Maps a native handle (the integer id the engine returns) back to its JS wrapper, so callbacks
2
- // the host fires by id (animation events, touch hits, media events) can be routed to the owning
3
- // object. Each engine/domain keeps its own Registry instance.
4
-
5
- export class Registry<T> {
6
- private map = new Map<number, T>()
7
-
8
- set(id: number, value: T): void {
9
- this.map.set(id, value)
10
- }
11
-
12
- get(id: number): T | undefined {
13
- return this.map.get(id)
14
- }
15
-
16
- delete(id: number): void {
17
- this.map.delete(id)
18
- }
19
-
20
- has(id: number): boolean {
21
- return this.map.has(id)
22
- }
23
- }
@@ -1,81 +0,0 @@
1
- // CameraView — the device camera (live preview + still capture) as a Presentable. A typed wrapper
2
- // over the `NativeView("camera")` capability (docs/navigation-presentable-plan.md): open it
3
- // fullscreen (`await camera.open()`), push it (`Router.push(camera)`), or embed it among a
4
- // screen's children as a live preview node. `takePhoto()` returns a `File` (a host buffer
5
- // handle) — use it as a texture (`Texture2D.load(file)`), upload it (`FormData`), or `share` it.
6
- //
7
- // Host-OPTIONAL: it exists where the host registers a "camera" view (Android; web can via
8
- // `_creatorUI._registerView`) — gate on `CameraView.isSupported`. Opening prepares first (camera
9
- // permission) while the current destination stays visible — the same contract as ARScene/QRScanner.
10
- //
11
- // The pre-Presentable `_creatorUtils.camera*` bridges are gone from the ABI (2026-07-15): hosts
12
- // keep their implementations only to serve previously compiled bundles.
13
-
14
- import { File } from "../runtime/fetch"
15
- import { _navEpoch, type PresentOptions } from "../ui/presentable"
16
- import { NativeView, NativeViewElement } from "../ui/NativeView"
17
- import { _requestCameraPermission } from "./permission"
18
-
19
- export type CameraFacing = "front" | "back"
20
-
21
- export interface CameraView extends NativeView {
22
- /** Request camera permission, then present the preview. The current destination stays visible
23
- * until the camera is ready; rejects if permission is denied, the host has no camera, or
24
- * another navigation superseded this one. */
25
- open(options?: PresentOptions): Promise<void>
26
- /** Capture a still frame as an image `File` (host-encoded). */
27
- takePhoto(): Promise<File>
28
- /** Switch between the front and back camera while the preview is live. */
29
- setFacingMode(facingMode: CameraFacing): Promise<void>
30
- }
31
-
32
- class CameraViewElement extends NativeViewElement {
33
- constructor(options: { facingMode?: CameraFacing } = {}) {
34
- super("camera", { facingMode: options.facingMode ?? "back" })
35
- }
36
-
37
- /** @internal prepare-then-present: permission before the swap (see Presentable._prepare). */
38
- async _prepare(): Promise<void> {
39
- const epoch = _navEpoch()
40
- await _requestCameraPermission()
41
- if (_navEpoch() !== epoch) {
42
- throw new Error("CameraView.open() superseded: another destination was opened while preparing")
43
- }
44
- }
45
-
46
- override async open(options?: PresentOptions): Promise<void> {
47
- // Fail before prompting for the camera — see QRScanner.
48
- if (!NativeView.isSupported("camera")) {
49
- throw new Error("CameraView isn't available on this host — it needs a registered \"camera\" view.")
50
- }
51
- await this._prepare()
52
- super.open(options)
53
- }
54
-
55
- async takePhoto(): Promise<File> {
56
- const photo = await this.call("takePhoto")
57
- return new File(photo.systemId, photo.name, photo.size)
58
- }
59
-
60
- async setFacingMode(facingMode: CameraFacing): Promise<void> {
61
- await this.call("setFacingMode", facingMode)
62
- }
63
- }
64
-
65
- /**
66
- * Create a camera view (default: the back camera). `CameraView.isSupported` reports whether this
67
- * host has a camera capability — check it before offering the feature.
68
- */
69
- // PURE IIFE so an app that never uses the camera tree-shakes the whole plugin away (see NativeView).
70
- export const CameraView: {
71
- (options?: { facingMode?: CameraFacing }): CameraView
72
- /** Whether this host registered a "camera" view. */
73
- readonly isSupported: boolean
74
- } = /*#__PURE__*/ (() => {
75
- const factory = (options?: { facingMode?: CameraFacing }): CameraView =>
76
- new CameraViewElement(options) as unknown as CameraView
77
- Object.defineProperty(factory, "isSupported", {
78
- get: (): boolean => NativeView.isSupported("camera"),
79
- })
80
- return factory as any
81
- })()
@@ -1,132 +0,0 @@
1
- // Push — remote notifications as a typed service plugin (docs/push-plan.md). A wrapper over
2
- // the host-registered "push" service, the Geolocation recipe applied to notifications: the
3
- // host owns the OS token, the permission prompt, and the platform registration with the
4
- // le.codes backend; the app only ever sees the opaque project-scoped ADDRESS (never the raw
5
- // APNs/FCM token) plus payloads.
6
- //
7
- // Host-OPTIONAL, like every plugin — gate on `Push.isSupported`. The OS permission prompt
8
- // happens inside `register()` (the first call that needs it, host-side); `getStatus()` and
9
- // `getLaunch()` never prompt. On hosts without the service every method rejects immediately,
10
- // BEFORE any permission path.
11
- //
12
- // The app is not running when a push arrives — display is host/OS work. The JS world sees
13
- // exactly three delivery paths:
14
- // - event "message": arrived while this app was foregrounded (the host suppresses the banner)
15
- // - event "tap": tapped while the world was alive (warm)
16
- // - getLaunch(): the notification that cold-started this world (stable pull, or null)
17
- // A payload's `url` additionally rides the app-lifecycle deep-link path (`app.launchUrl` /
18
- // the "url" event), so apps that already route links get notification routing for free.
19
- //
20
- // Service contract ("push"): calls `getStatus() → PushStatus`, `register({user?}) → {address}`
21
- // (rejects "denied"/"unavailable"), `unregister()`, `getLaunch() → PushPayload|null`;
22
- // events `message`/`tap` → PushPayload. An OPEN SESSION is the host's delivery pipe for the
23
- // events — addEventListener opens one eagerly (cheap by the channel contract: no prompt, no I/O).
24
-
25
- import { ServiceClient } from "../runtime/service"
26
-
27
- /** A delivered notification, as the app sees it. */
28
- export interface PushPayload {
29
- title: string
30
- body?: string
31
- /** Deep link — also delivered through `app.launchUrl` / the app "url" event. */
32
- url?: string
33
- /** The developer's own JSON, ≤ 2 KB serialized. */
34
- data?: Record<string, any>
35
- badge?: number
36
- }
37
-
38
- export interface PushStatus {
39
- /** OS notification permission for the host app. "prompt" = not asked yet. */
40
- permission: "granted" | "denied" | "prompt"
41
- /** Whether this device already holds a registration (an address) for this app. */
42
- registered: boolean
43
- }
44
-
45
- export interface PushRegisterOptions {
46
- /** Your app's OWN user id ("external user id"): lets your server send to `users: ["u42"]`
47
- * instead of storing addresses, fanning out to all the user's devices. CLIENT-ASSERTED and
48
- * unverified — never target sensitive content by user. The registration reflects the latest
49
- * `register()` call, so registering without `user` clears it (logout). */
50
- user?: string
51
- }
52
-
53
- export type PushEvent = "message" | "tap"
54
-
55
- /**
56
- * Remote push notifications. `Push.isSupported` reports whether this host registered the
57
- * service — check it before offering notification features.
58
- */
59
- // PURE IIFE so an app that never uses push tree-shakes the whole plugin away (see Geolocation).
60
- export const Push: {
61
- /** Whether this host registered a "push" service. */
62
- readonly isSupported: boolean
63
- /** Permission + registration state. Never prompts. */
64
- getStatus(): Promise<PushStatus>
65
- /** Ask for permission (first time) and register this device → the opaque address your server
66
- * targets. Idempotent — safe to call every launch; rejects "denied" / "unavailable". */
67
- register(options?: PushRegisterOptions): Promise<{ address: string }>
68
- /** Remove this device's registration (the address stops receiving). */
69
- unregister(): Promise<void>
70
- /** The notification that cold-started this app run, or null. Stable across calls. */
71
- getLaunch(): Promise<PushPayload | null>
72
- /** "message" = received while the app is open; "tap" = the user tapped one while it ran. */
73
- addEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void
74
- removeEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void
75
- } = /*#__PURE__*/ (() => {
76
- let client: ServiceClient | null = null
77
- const service = (): ServiceClient => client ??= new ServiceClient("push")
78
-
79
- const assertSupported = (): void => {
80
- // Fail before the host is touched — a permission dialog for a feature that can't exist
81
- // would be worse than the error (same ordering rule as QRScanner/Geolocation).
82
- if (!ServiceClient.isSupported("push")) {
83
- throw new Error('Push isn\'t available on this host — it needs a registered "push" service.')
84
- }
85
- }
86
-
87
- // ServiceClient.on only records listeners; events need a live session to arrive on. Poke the
88
- // host with the prompt-free getStatus once so a listener-only app (already registered on a
89
- // previous run) still hears warm taps.
90
- let pipeOpened = false
91
- const ensurePipe = (): void => {
92
- if (pipeOpened || !ServiceClient.isSupported("push")) return
93
- pipeOpened = true
94
- service().call("getStatus").catch(() => { pipeOpened = false })
95
- }
96
-
97
- const push = {
98
- async getStatus(): Promise<PushStatus> {
99
- assertSupported()
100
- return await service().call("getStatus")
101
- },
102
-
103
- async register(options?: PushRegisterOptions): Promise<{ address: string }> {
104
- assertSupported()
105
- return await service().call("register", options ?? {})
106
- },
107
-
108
- async unregister(): Promise<void> {
109
- assertSupported()
110
- await service().call("unregister")
111
- },
112
-
113
- async getLaunch(): Promise<PushPayload | null> {
114
- assertSupported()
115
- return (await service().call("getLaunch")) ?? null
116
- },
117
-
118
- addEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void {
119
- // Recording on an unsupported host is a silent no-op (the events just never fire),
120
- // like app/device listeners — only the promise-returning methods reject loudly.
121
- service().on(event, callback as (data?: any) => void)
122
- ensurePipe()
123
- },
124
- removeEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void {
125
- service().off(event, callback as (data?: any) => void)
126
- },
127
- }
128
- Object.defineProperty(push, "isSupported", {
129
- get: (): boolean => ServiceClient.isSupported("push"),
130
- })
131
- return push as any
132
- })()
package/src/plugins/qr.ts DELETED
@@ -1,73 +0,0 @@
1
- // QRScanner — the host's camera QR scanner as a Presentable. A typed wrapper over the
2
- // `NativeView("qrScanner")` capability (docs/navigation-presentable-plan.md): open it fullscreen
3
- // (`await scanner.open()`), push it (`Router.push(scanner)`), or embed it among a screen's
4
- // children as a live preview node — everything a NativeView can do, plus a typed `onScan`.
5
- //
6
- // Host-OPTIONAL, like every plugin: it exists where the host registers a "qrScanner" view
7
- // (Android; web can via `_creatorUI._registerView`) — gate on `QRScanner.isSupported`. Opening
8
- // prepares first (camera permission) while the current destination stays visible — the same
9
- // prepare-then-present contract as ARScene.
10
- //
11
- // The pre-Presentable `_creatorUtils.openScanner/closeScanner` bridges are gone from the ABI
12
- // (2026-07-15): hosts keep their implementations only to serve previously compiled bundles.
13
-
14
- import { _navEpoch, type PresentOptions } from "../ui/presentable"
15
- import { NativeView, NativeViewElement } from "../ui/NativeView"
16
- import { _requestCameraPermission } from "./permission"
17
-
18
- export interface QRScanner extends NativeView {
19
- /** Fires per decoded camera frame: the decoded string, or `null` for a frame with no readable
20
- * code (expect those repeatedly while the user aims). The same code can be reported more than
21
- * once — `close()` or debounce once you have what you need. */
22
- onScan(callback: (data: string | null) => void): this
23
- /** Request camera permission, then present the scanner. The current destination (and its
24
- * loading state) stays visible until the scanner is ready; rejects if the camera is denied,
25
- * the host can't scan, or another navigation superseded this one. */
26
- open(options?: PresentOptions): Promise<void>
27
- }
28
-
29
- class QRScannerElement extends NativeViewElement {
30
- constructor() {
31
- super("qrScanner", {})
32
- }
33
-
34
- onScan(callback: (data: string | null) => void): this {
35
- return this.on("scan", (payload?: { data?: string | null }) => callback(payload?.data ?? null))
36
- }
37
-
38
- /** @internal prepare-then-present: permission before the swap (see Presentable._prepare). */
39
- async _prepare(): Promise<void> {
40
- const epoch = _navEpoch()
41
- await _requestCameraPermission()
42
- if (_navEpoch() !== epoch) {
43
- throw new Error("QRScanner.open() superseded: another destination was opened while preparing")
44
- }
45
- }
46
-
47
- override async open(options?: PresentOptions): Promise<void> {
48
- // Fail before prompting for the camera — a permission dialog for a scanner that can't exist
49
- // would be worse than the error.
50
- if (!NativeView.isSupported("qrScanner")) {
51
- throw new Error("QRScanner isn't available on this host — it needs a registered \"qrScanner\" view.")
52
- }
53
- await this._prepare()
54
- super.open(options)
55
- }
56
- }
57
-
58
- /**
59
- * Create a QR scanner view. `QRScanner.isSupported` reports whether this host can scan at all —
60
- * check it before offering the feature.
61
- */
62
- // PURE IIFE so an app that never scans tree-shakes the whole plugin away (see NativeView).
63
- export const QRScanner: {
64
- (): QRScanner
65
- /** Whether this host registered a "qrScanner" view. */
66
- readonly isSupported: boolean
67
- } = /*#__PURE__*/ (() => {
68
- const factory = (): QRScanner => new QRScannerElement() as unknown as QRScanner
69
- Object.defineProperty(factory, "isSupported", {
70
- get: (): boolean => NativeView.isSupported("qrScanner"),
71
- })
72
- return factory as any
73
- })()