lecodes-sdk 2.0.3 → 2.0.5

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 (84) hide show
  1. package/README.md +104 -76
  2. package/dist/global.d.ts +3 -5
  3. package/dist/host.d.ts +3 -0
  4. package/dist/types/inject.d.ts +4 -4
  5. package/dist/types/net/codec.d.ts +3 -2
  6. package/dist/types/net/core.d.ts +10 -1
  7. package/dist/types/net/index.d.ts +22 -5
  8. package/dist/types/net/replication.d.ts +23 -2
  9. package/dist/types/runtime/device.d.ts +7 -0
  10. package/dist/types/runtime/rpc.d.ts +11 -17
  11. package/dist/types/runtime/wire.d.ts +53 -0
  12. package/dist/types/server/auth/api.d.ts +42 -0
  13. package/dist/types/server/auth/appConfig.d.ts +1 -5
  14. package/dist/types/server/auth/models.d.ts +119 -70
  15. package/dist/types/server/auth/types.d.ts +19 -43
  16. package/dist/types/server/channel.d.ts +57 -19
  17. package/dist/types/server/context.d.ts +2 -2
  18. package/dist/types/server/db/defineDb.d.ts +10 -0
  19. package/dist/types/server/db/index.d.ts +1 -1
  20. package/dist/types/server/db/types.d.ts +76 -6
  21. package/dist/types/server/inject.d.ts +0 -1
  22. package/dist/types/ui/UINode.d.ts +19 -5
  23. package/dist/types/ui/UIScreen.d.ts +1 -0
  24. package/dist/types/ui/UITabs.d.ts +8 -6
  25. package/dist/types/ui/theme.d.ts +48 -13
  26. package/dist/types/version.d.ts +1 -1
  27. package/dist/types.json +1 -1
  28. package/package.json +4 -2
  29. package/prompts/README.md +1 -1
  30. package/prompts/design.md +19 -19
  31. package/prompts/dist/2d-game.md +45 -31
  32. package/prompts/dist/3d-app.md +45 -31
  33. package/prompts/dist/ar-app.md +45 -31
  34. package/prompts/dist/design.md +25 -24
  35. package/prompts/dist/ui-app.md +45 -31
  36. package/prompts/ui-design.md +6 -5
  37. package/prompts/ui.md +25 -22
  38. package/src/animate/tween/read.ts +146 -0
  39. package/src/bridges/device.d.ts +9 -0
  40. package/src/bridges/tree.d.ts +5 -0
  41. package/src/canvas/gen/cssColor.ts +1 -1
  42. package/src/canvas/gen/recorder.ts +1 -1
  43. package/src/canvas/gen/spec.ts +1 -1
  44. package/src/chisel.ts +1 -1
  45. package/src/compile/bundler.ts +6 -0
  46. package/src/compile/compileProject.ts +3 -1
  47. package/src/compile/index.ts +3 -1
  48. package/src/compile/serverSplit.ts +58 -11
  49. package/src/compile/serverTypes.ts +189 -8
  50. package/src/host.d.ts +3 -0
  51. package/src/inject.ts +7 -7
  52. package/src/net/codec.ts +19 -10
  53. package/src/net/core.ts +18 -5
  54. package/src/net/index.ts +30 -9
  55. package/src/net/replication.ts +63 -28
  56. package/src/runtime/device.ts +12 -0
  57. package/src/runtime/rpc.ts +101 -40
  58. package/src/runtime/wire.ts +35 -0
  59. package/src/server/auth/api.ts +94 -0
  60. package/src/server/auth/appConfig.ts +2 -3
  61. package/src/server/auth/host.ts +244 -174
  62. package/src/server/auth/models.ts +45 -62
  63. package/src/server/auth/types.ts +19 -34
  64. package/src/server/channel.ts +97 -29
  65. package/src/server/channelHub.ts +153 -0
  66. package/src/server/context.ts +2 -2
  67. package/src/server/db/defineDb.ts +96 -36
  68. package/src/server/db/index.ts +1 -1
  69. package/src/server/db/types.ts +76 -8
  70. package/src/server/host.ts +25 -10
  71. package/src/server/inject.ts +2 -2
  72. package/src/server/runtime.ts +34 -12
  73. package/src/ui/UINode.ts +22 -5
  74. package/src/ui/UIScreen.ts +5 -0
  75. package/src/ui/UITabs.ts +19 -17
  76. package/src/ui/styleColor.ts +10 -1
  77. package/src/ui/theme.ts +96 -41
  78. package/src/version.ts +1 -1
  79. package/tests/helpers/fakeTree.ts +1 -0
  80. package/dist/types/plugins/oauth.d.ts +0 -25
  81. package/dist/types/server/auth/global.d.ts +0 -56
  82. package/src/plugins/oauth.ts +0 -61
  83. package/src/server/auth/global.ts +0 -80
  84. package/tests/helpers/memoryMarci.ts +0 -124
@@ -191,7 +191,7 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
191
191
  // Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
192
192
  // Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
193
193
  // Background (containers/screen/button; UIText/UIImage/UISpacer have bgColor only):
194
- // bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"tile"), bgGradient
194
+ // bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
195
195
  // bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)".
196
196
  // Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
197
197
  // bgImage is decoration BEHIND children — an image that IS the content belongs in UIImage.
@@ -221,10 +221,11 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
221
221
 
222
222
  // --- Theme variables ---
223
223
  // theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px):
224
- const T = theme({ primaryColor: "#0A84FF" })
225
- label.style({ color: T.primaryColor }) // or "var(--primaryColor)"
226
- // theme({ color, fontFamily }) drive the DEFAULT text color/font app-wide. "var(--name, fallback)"
227
- // applies the fallback while the key is unset — how the SDK tab bar (defineTabs) stays themeable.
224
+ const T = theme({ accent: "#0A84FF" })
225
+ label.style({ color: T.accent }) // or "var(--accent)"
226
+ // ROLES the SDK reads itself: bg (screens) · surface / border (the tab bar) · text / fontFamily
227
+ // (text that names none) · textMuted (inactive tabs) · accent (the active tab). "var(--name,
228
+ // fallback)" applies the fallback while the key is unset.
228
229
 
229
230
  // --- Defaults that surprise ---
230
231
  // flexShrink: 0 — elements don't shrink to fit (exception: UIScrollable defaults flexShrink: 1 so it scrolls
@@ -327,20 +328,20 @@ composition:
327
328
 
328
329
  ```ts
329
330
  // design/shared/tokens.ts — surfaces dark-to-light, text in tiers, accents as a family.
330
- // ONE theme() call registers the vars and returns the accessors ("var(--x)" strings); the system
331
- // key `color` in the same table sets the app-wide default text color. Re-calling theme() with new
332
- // values (dark mode, brand swap) restyles every screen — and later the built app — live.
333
- const palette = {
334
- bg: "#080808", card: "#161618", cardAlt: "#1d1d20", field: "#101012",
335
- border: "#2a2a2e", text: "#ffffff", muted: "#8a8a93", faint: "#5a5b62",
336
- accent: "#00BFC2", accentPressed: "#00999b", accentDeep: "#0c4e4f", onAccent: "#05201f",
331
+ // ONE theme() call registers the vars and returns the accessors ("var(--x)" strings). Re-calling
332
+ // theme() with new values (dark mode, brand swap) restyles every screen — and later the built app
333
+ // — live.
334
+ export const colors = theme({
335
+ // the ROLES — the SDK reads these too: bg = every screen's background, text = text that names
336
+ // no color, surface / border / textMuted / accent = the tab bar (defineTabs here, UITabs in the
337
+ // built app). Name the palette by them; there are no separate keys for the chrome.
338
+ bg: "#080808", surface: "#161618", border: "#2a2a2e",
339
+ text: "#ffffff", textMuted: "#8a8a93", accent: "#00BFC2",
340
+ // the design's own
341
+ surfaceAlt: "#1d1d20", field: "#101012", faint: "#5a5b62",
342
+ accentPressed: "#00999b", accentDeep: "#0c4e4f", onAccent: "#05201f",
337
343
  success: "#30D158", danger: "#F83131", scrim: "#000000b3",
338
- // theme SYSTEM keys — SDK-rendered chrome reads these. `color` = default text color everywhere;
339
- // the other four brand the tab bar (defineTabs here, UITabs in the built app). Mirror the brand:
340
- color: "#ffffff",
341
- primaryColor: "#00BFC2", mutedColor: "#8a8a93", tabbarBg: "#161618", screenBg: "#080808",
342
- }
343
- export const colors: { [K in keyof typeof palette]: string } = theme(palette)
344
+ })
344
345
  export const spacing = { xs: 4, sm: 8, md: 12, lg: 16, xl: 20, xxl: 24 }
345
346
  export const radius = { sm: 8, md: 12, lg: 16, pill: 999 }
346
347
  ```
@@ -350,7 +351,7 @@ export const radius = { sm: 8, md: 12, lg: 16, pill: 999 }
350
351
  import { colors, spacing, radius } from "./tokens"
351
352
 
352
353
  export const PrimaryButton = (label: string, o: { name?: string, disabled?: boolean } = {}) =>
353
- UIButton(UIText(label).style({ fontSize: 16, fontWeight: 700, color: o.disabled ? colors.muted : colors.onAccent }))
354
+ UIButton(UIText(label).style({ fontSize: 16, fontWeight: 700, color: o.disabled ? colors.textMuted : colors.onAccent }))
354
355
  .style({ height: 52, borderRadius: radius.md, bgColor: o.disabled ? colors.accentDeep : colors.accent,
355
356
  justifyContent: "center", alignItems: "center", name: o.name, $pressed: { bgColor: colors.accentPressed } })
356
357
 
@@ -361,7 +362,7 @@ export const SectionLabel = (t: string) =>
361
362
  export const Field = (o: { label?: string, value?: string, placeholder?: string, focused?: boolean }) =>
362
363
  UIColumn(
363
364
  o.label ? SectionLabel(o.label) : null,
364
- UIRow(UIText(o.value ?? o.placeholder ?? "").style({ flexGrow: 1, fontSize: 16, color: o.value ? colors.text : colors.muted }))
365
+ UIRow(UIText(o.value ?? o.placeholder ?? "").style({ flexGrow: 1, fontSize: 16, color: o.value ? colors.text : colors.textMuted }))
365
366
  .style({ height: 52, px: 16, alignItems: "center", bgColor: colors.field, borderRadius: radius.md,
366
367
  border: `1px solid ${o.focused ? colors.accent : colors.border}` }),
367
368
  ).style({ gap: spacing.sm })
@@ -380,9 +381,9 @@ export const ConfirmDialog = (o: { title: string, body?: string, confirmLabel: s
380
381
  UIWidget(
381
382
  UIColumn(
382
383
  UIText(o.title).style({ fontSize: 17, fontWeight: 700, color: colors.text, textAlign: "center" }),
383
- o.body ? UIText(o.body).style({ fontSize: 14, color: colors.muted, textAlign: "center", lineHeight: "1.4em" }) : null,
384
+ o.body ? UIText(o.body).style({ fontSize: 14, color: colors.textMuted, textAlign: "center", lineHeight: "1.4em" }) : null,
384
385
  PrimaryButton("Cancel", { name: "cancel" }),
385
- ).style({ bgColor: colors.card, borderRadius: radius.lg, p: spacing.xl, gap: spacing.lg, width: "86%", maxWidth: 360 }),
386
+ ).style({ bgColor: colors.surface, borderRadius: radius.lg, p: spacing.xl, gap: spacing.lg, width: "86%", maxWidth: 360 }),
386
387
  ).style({ top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center", overlayColor: colors.scrim })
387
388
  ```
388
389
 
@@ -479,8 +480,8 @@ export const mainTabs = defineTabs({
479
480
  A screen that shows the bar mounts it with a literal tab id — roots AND pushed details that keep
480
481
  the bar; a screen that doesn't mount it is a full-screen push. Mount the bar alone when the screen
481
482
  owns its layout (`mainTabs.bar("schedule")`, as above) or wrap content with
482
- `mainTabs.screen("home", [...])`. Restyle through the theme's system keys — `primaryColor`,
483
- `mutedColor`, `tabbarBg`, `screenBg`, set in `shared/tokens.ts` — never by reimplementing the
483
+ `mainTabs.screen("home", [...])`. It is colored by the theme's roles — `accent`,
484
+ `textMuted`, `surface`, `border`, `bg`, set in `shared/tokens.ts` — never by reimplementing the
484
485
  bar. Keep the `defineTabs({...})` declaration and `.screen("<tab>", ...)` / `.bar("<tab>")` call
485
486
  shapes intact — the board parses them.
486
487
 
@@ -403,8 +403,8 @@ Router.init(tabs) // UITabs IS a UIScreen — present it directly
403
403
  // .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
404
404
  // .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
405
405
  // .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
406
- // Styling is THEME-driven: theme({ primaryColor, mutedColor, tabbarBg, tabbarBorder, badgeColor })
407
- // restyles the bar app-wide (dark fallbacks built in). For a custom bar layout use UIPager below.
406
+ // The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
407
+ // own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
408
408
 
409
409
  // UIPager — the navigation primitive under UITabs: sibling tabs that swipe natively, each tab its
410
410
  // OWN push/pop stack. Reach for it directly for a plain stack (one-screen pager) or a fully custom
@@ -620,7 +620,7 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
620
620
  // Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
621
621
  // Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
622
622
  // Background (containers/screen/button/input/video; UIText/UIImage/UISpacer have bgColor only):
623
- // bgColor, bgImage (string | FetchResponse | File), bgSize ("cover"|"contain"|"tile"), bgGradient
623
+ // bgColor, bgImage (string | FetchResponse | File | SvgSource), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
624
624
  // bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)"
625
625
  // or "radial-gradient(circle at 50% 40%, #7B2FF7, #0a0a1a)". Comma-separate several gradients to stack them (first = on top).
626
626
  // Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
@@ -647,23 +647,27 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
647
647
  // Env keywords resolving to the device insets (notch, home indicator):
648
648
  // "safe-top" | "safe-bottom" | "safe-left" | "safe-right"; "safe-all" — p / m shorthands only
649
649
  // Comfort tokens — where content comfortably starts, never collapse to 0: max(safe-edge, knob) on clearance
650
- // insets (iOS notch/home indicator, gesture nav); past exact-height system BARS (Android status/nav bar) the
651
- // knob ADDS instead. Knob defaults: 12 top, 4 bottom, 16 horizontal (the page gutter):
650
+ // insets (iOS notch/home indicator); past exact-height system BARS (Android status/nav bar) the
651
+ // knob ADDS instead. Knob defaults: 12 top, 8 bottom, 16 horizontal (the page gutter):
652
652
  // "comfort-top" | "comfort-bottom" | "comfort-left" | "comfort-right"
653
653
  // "comfort-x" — px/mx | "comfort-y" — py/my | "comfort-all" — p / m
654
654
  // Accepted on per-side padding & margin, position offsets (top/left/bottom/right), and inside calc()/min()/max():
655
- .style({ pb: "comfort-bottom" }) // bottom bar: 34 over a home indicator, 52 above Android's button bar, 4 on inset-less devices
655
+ .style({ pb: "comfort-bottom" }) // bottom bar: 34 over a home indicator, 56 above Android's button bar, 8 on inset-less devices
656
656
  .style({ px: "comfort-x" }) // page gutter that also clears the notch in landscape
657
657
  .style({ pb: "calc(comfort-bottom + 56px)" }) // scroll content clearing a 56px floating bar
658
658
 
659
659
  // --- Theme variables ---
660
660
  // theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px
661
661
  // lengths, null removes a key). Re-calling re-styles the LIVE UI — dark mode is a second call.
662
- // Returned accessors ARE the var() strings; system vars are statics (theme.primaryColor, …):
663
- const T = theme({ primaryColor: "#0A84FF", "comfort-left": 20 })
664
- label.style({ color: T.primaryColor, pl: "var(--comfort-left)" })
665
- // SYSTEM DEFAULTS: theme({ color, fontFamily }) drive the DEFAULT text color/font — all text
666
- // without explicit values follows (light theme = set once, not color:"black" per label).
662
+ // ONE call per app. Returned accessors ARE the var() strings:
663
+ const T = theme({ accent: "#0A84FF", "comfort-left": 20 })
664
+ label.style({ color: T.accent, pl: "var(--comfort-left)" })
665
+ // ROLES — keys the SDK reads itself; use them as your palette and add your own keys beside them:
666
+ // bg (every screen's background) · surface (cards, bars; the tab bar) · border (hairlines)
667
+ // text (text with no color of its own) · textMuted (secondary; inactive tabs) · accent (active tab)
668
+ // fontFamily (text with no font of its own). Light theme = set text once, not a color per label.
669
+ // SCOPE: el.theme({...}) — the same keys for ONE subtree (a card, tabs.bar, a whole screen); its
670
+ // descendants read them first. A pushed screen / a widget is its own root: give it its own.
667
671
  // Bare comfort-* tokens apply the safe-area formula; "var(--comfort-top)" reads the raw knob.
668
672
  // "var(--name, fallback)" applies the fallback while the key is unset. Env names (safe-*, vw/vh) are not theme keys.
669
673
 
@@ -695,7 +699,7 @@ screen.style({ flexDirection: "column", p: 16, $landscape: { flexDirection: "row
695
699
  // alignItems: "stretch" — children FILL the cross axis by default; set a size or alignSelf to opt out.
696
700
  // overflow: "hidden" — children are clipped to the parent's box; set overflow: "visible" to let them escape.
697
701
  // boxSizing: "border-box" — width/height include padding & border.
698
- // Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({ color, fontFamily })).
702
+ // Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({ bg, text, fontFamily })).
699
703
  // On any light surface — white cards, sheets, inputs — white text renders invisible: use dark text there
700
704
  // ("#111"/black), checking every UIText against the surface it actually sits on, not the screen.
701
705
 
@@ -769,14 +773,25 @@ UIButton().onLayout(({ width }) => { buttonWidth = width })
769
773
  // ===== ROUTER (multi-page apps) =====
770
774
  // Tabs are UITabs' job, in-tab stacks UIPager's (see UI COMPONENTS) — the Router owns what sits ABOVE the shell.
771
775
  Router.init(homeScreen, opts?: { showDefaultBackButton?: boolean }) // call once in the entry file (default false)
772
- Router.push(screen) // push onto the stack, screen becomes active
773
- Router.pop(to?: number) // default -1 = one back; negative = relative (-2 = back two), 0/positive = absolute
776
+ Router.push(screen, opts?: { transition, popTransition }) // push onto the stack, screen becomes active (default "push")
777
+ Router.pop(to?: number, opts?: { transition })
778
+ // default -1 = one back; negative = relative (-2 = back two), 0/positive = absolute
774
779
  // stack index (0 = home). Popped screens' native trees are destroyed; the UIScreen
775
- // object stays reusable (push remounts it).
776
- Router.replace(screen, opts?: { transition }) // swap the top screen; transition: "slide-from-left" |
777
- // "slide-from-right" | "slide-from-top" | "slide-from-bottom" | "zoom" | "zoom-out" | "zoom-in" | "fade" | "none" (default "fade")
780
+ // object stays reusable (push remounts it). Plays the way back the screen remembers.
781
+ Router.replace(screen, opts?: { transition, popTransition }) // swap the top screen (default "none"; theme({ replaceTransition }))
778
782
  Router.current // the active UIScreen (getter)
779
- Router.hide() / Router.restore() // hide/restore the whole router — stack stays alive; fires the top screen's onClose/onOpen
783
+ Router.hide(opts?) / Router.restore(opts?) // hide/restore the whole router — stack stays alive; fires the top screen's onClose/onOpen
784
+ // TRANSITIONS — every call above, and open() / close() of any destination, takes { transition }:
785
+ // a name: "push" | "pop" | "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom" |
786
+ // "zoom" | "zoom-in" | "zoom-out" | "fade" | "none"
787
+ // or your own: { enter?: pose, exit?: pose, onTop?: "enter" | "exit" } — the incoming screen comes FROM `enter`,
788
+ // the outgoing one goes TO `exit`. pose = { transform?, opacity?, dim?, duration? (ms, 300), delay?, easing? };
789
+ // arrays are keyframes; in a pose a % in translate is a percent of the screen ("translateY(100%)").
790
+ // e.g. Router.push(page, { transition: { enter: { transform: "translateY(100%)", duration: 400 }, exit: { dim: 0.3 } } })
791
+ // popTransition = the way BACK the screen remembers (Router.pop(), the back gesture, the system's back play it).
792
+ // Default: the transition it came with, the other way round (a slide from the bottom leaves downwards;
793
+ // a zoom / a fade go back with a fade). A replace without one keeps the way back of the screen it replaces.
794
+ // A screen's own transform / opacity are not seen while a transition moves it — style a child instead.
780
795
  Router.addEventListener("change", (screen: UIScreen) => ...) / .removeEventListener("change", cb) // after every navigation
781
796
  // Screens BELOW the top stay mounted — element trees and state survive, they're just not rendered.
782
797
  // push fires the outgoing screen's onClose + the incoming one's onOpen; pop fires them in reverse and
@@ -798,7 +813,7 @@ font("ibm-plex-sans", { weights: [400, 600], italic: true }) // narrow/extend t
798
813
  // A project's own file: font("./fonts/Brand.ttf") → real family name read from the file itself
799
814
  // (weight/italic too — no options); two weight files of one family return the same name.
800
815
  // registerFont(family, url, {weight, style}) survives ONLY for runtime-computed URLs — rare.
801
- // No style inheritance — set fontFamily per UIText or via theme() (extract a shared Style<UIText>).
816
+ // The app's font: theme({ fontFamily }). A second family is set per UIText (extract a shared Style<UIText>).
802
817
 
803
818
  // ===== SVG IMAGES =====
804
819
  // SvgSource wraps raw SVG XML for use as an image source: UIImage(SvgSource(`<svg ...>`)).
@@ -900,17 +915,16 @@ screen.open()
900
915
  </file>
901
916
 
902
917
  // === EXAMPLE 2: Theme tokens + fetched list with loading / error states ===
903
- // A tokens module every screen imports, light-themed: theme({ color }) sets the default text
904
- // color ONCE — no color: "#111" on every label.
918
+ // A tokens module every screen imports, light-themed. ONE theme() call: the roles (bg, surface,
919
+ // border, text, textMuted, accent) color the screens, the default text and the tab bar — no
920
+ // color: "#111" on every label — and the app's own keys sit beside them.
905
921
  <file name="tokens.ts">
906
- const palette = {
907
- bg: "#F4F6F5", card: "#FFFFFF", border: "#E4E8E6",
908
- text: "#131A17", muted: "#606B65",
909
- accent: "#15A34A", accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
910
- }
911
- theme({ color: palette.text, primaryColor: palette.accent })
912
922
  // Accessors ARE "var(--x)" strings — re-calling theme() with new values restyles the live app.
913
- export const colors: { [K in keyof typeof palette]: string } = theme(palette)
923
+ export const colors = theme({
924
+ bg: "#F4F6F5", surface: "#FFFFFF", border: "#E4E8E6",
925
+ text: "#131A17", textMuted: "#606B65", accent: "#15A34A",
926
+ accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
927
+ })
914
928
  export const font = { // type scale — spread into styles: .style({ ...font.h2 })
915
929
  h2: { fontSize: 22, fontWeight: 700 }, bodyStrong: { fontSize: 16, fontWeight: 600 },
916
930
  small: { fontSize: 14 }, tiny: { fontSize: 12, fontWeight: 500 },
@@ -927,9 +941,9 @@ const Row = (u: User) => UIRow(
927
941
  justifyContent: "center", alignItems: "center" }),
928
942
  UIColumn(
929
943
  UIText(u.name).style({ ...font.bodyStrong }),
930
- UIText(u.email).style({ ...font.small, color: colors.muted }),
944
+ UIText(u.email).style({ ...font.small, color: colors.textMuted }),
931
945
  ).style({ flexGrow: 1, flexShrink: 1, gap: 2, alignItems: "flex-start" }),
932
- ).style({ alignItems: "center", gap: 12, bgColor: colors.card, borderRadius: 16,
946
+ ).style({ alignItems: "center", gap: 12, bgColor: colors.surface, borderRadius: 16,
933
947
  border: `1px solid ${colors.border}`, p: 14 })
934
948
 
935
949
  const Centered = (...children: UINodeChild[]) =>
@@ -938,7 +952,7 @@ const Centered = (...children: UINodeChild[]) =>
938
952
  let body: UIColumn
939
953
 
940
954
  const loadData = async () => {
941
- body.setContent([Centered(UIText("Loading…").style({ color: colors.muted }))])
955
+ body.setContent([Centered(UIText("Loading…").style({ color: colors.textMuted }))])
942
956
  const res = await fetch("https://jsonplaceholder.typicode.com/users")
943
957
  if (res.status !== 200) {
944
958
  body.setContent([Centered(
@@ -92,7 +92,7 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
92
92
  // Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
93
93
  // Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
94
94
  // Background (containers/screen/button; UIText/UIImage/UISpacer have bgColor only):
95
- // bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"tile"), bgGradient
95
+ // bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
96
96
  // bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)".
97
97
  // Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
98
98
  // bgImage is decoration BEHIND children — an image that IS the content belongs in UIImage.
@@ -122,10 +122,11 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
122
122
 
123
123
  // --- Theme variables ---
124
124
  // theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px):
125
- const T = theme({ primaryColor: "#0A84FF" })
126
- label.style({ color: T.primaryColor }) // or "var(--primaryColor)"
127
- // theme({ color, fontFamily }) drive the DEFAULT text color/font app-wide. "var(--name, fallback)"
128
- // applies the fallback while the key is unset — how the SDK tab bar (defineTabs) stays themeable.
125
+ const T = theme({ accent: "#0A84FF" })
126
+ label.style({ color: T.accent }) // or "var(--accent)"
127
+ // ROLES the SDK reads itself: bg (screens) · surface / border (the tab bar) · text / fontFamily
128
+ // (text that names none) · textMuted (inactive tabs) · accent (the active tab). "var(--name,
129
+ // fallback)" applies the fallback while the key is unset.
129
130
 
130
131
  // --- Defaults that surprise ---
131
132
  // flexShrink: 0 — elements don't shrink to fit (exception: UIScrollable defaults flexShrink: 1 so it scrolls
package/prompts/ui.md CHANGED
@@ -42,8 +42,8 @@ Router.init(tabs) // UITabs IS a UIScreen — present it directly
42
42
  // .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
43
43
  // .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
44
44
  // .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
45
- // Styling is THEME-driven: theme({ primaryColor, mutedColor, tabbarBg, tabbarBorder, badgeColor })
46
- // restyles the bar app-wide (dark fallbacks built in). For a custom bar layout use UIPager below.
45
+ // The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
46
+ // own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
47
47
 
48
48
  // UIPager — the navigation primitive under UITabs: sibling tabs that swipe natively, each tab its
49
49
  // OWN push/pop stack. Reach for it directly for a plain stack (one-screen pager) or a fully custom
@@ -259,7 +259,7 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
259
259
  // Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
260
260
  // Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
261
261
  // Background (containers/screen/button/input/video; UIText/UIImage/UISpacer have bgColor only):
262
- // bgColor, bgImage (string | FetchResponse | File), bgSize ("cover"|"contain"|"tile"), bgGradient
262
+ // bgColor, bgImage (string | FetchResponse | File | SvgSource), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
263
263
  // bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)"
264
264
  // or "radial-gradient(circle at 50% 40%, #7B2FF7, #0a0a1a)". Comma-separate several gradients to stack them (first = on top).
265
265
  // Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
@@ -298,11 +298,15 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
298
298
  // --- Theme variables ---
299
299
  // theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px
300
300
  // lengths, null removes a key). Re-calling re-styles the LIVE UI — dark mode is a second call.
301
- // Returned accessors ARE the var() strings; system vars are statics (theme.primaryColor, …):
302
- const T = theme({ primaryColor: "#0A84FF", "comfort-left": 20 })
303
- label.style({ color: T.primaryColor, pl: "var(--comfort-left)" })
304
- // SYSTEM DEFAULTS: theme({ color, fontFamily }) drive the DEFAULT text color/font — all text
305
- // without explicit values follows (light theme = set once, not color:"black" per label).
301
+ // ONE call per app. Returned accessors ARE the var() strings:
302
+ const T = theme({ accent: "#0A84FF", "comfort-left": 20 })
303
+ label.style({ color: T.accent, pl: "var(--comfort-left)" })
304
+ // ROLES — keys the SDK reads itself; use them as your palette and add your own keys beside them:
305
+ // bg (every screen's background) · surface (cards, bars; the tab bar) · border (hairlines)
306
+ // text (text with no color of its own) · textMuted (secondary; inactive tabs) · accent (active tab)
307
+ // fontFamily (text with no font of its own). Light theme = set text once, not a color per label.
308
+ // SCOPE: el.theme({...}) — the same keys for ONE subtree (a card, tabs.bar, a whole screen); its
309
+ // descendants read them first. A pushed screen / a widget is its own root: give it its own.
306
310
  // Bare comfort-* tokens apply the safe-area formula; "var(--comfort-top)" reads the raw knob.
307
311
  // "var(--name, fallback)" applies the fallback while the key is unset. Env names (safe-*, vw/vh) are not theme keys.
308
312
 
@@ -334,7 +338,7 @@ screen.style({ flexDirection: "column", p: 16, $landscape: { flexDirection: "row
334
338
  // alignItems: "stretch" — children FILL the cross axis by default; set a size or alignSelf to opt out.
335
339
  // overflow: "hidden" — children are clipped to the parent's box; set overflow: "visible" to let them escape.
336
340
  // boxSizing: "border-box" — width/height include padding & border.
337
- // Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({ color, fontFamily })).
341
+ // Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({ bg, text, fontFamily })).
338
342
  // On any light surface — white cards, sheets, inputs — white text renders invisible: use dark text there
339
343
  // ("#111"/black), checking every UIText against the surface it actually sits on, not the screen.
340
344
 
@@ -448,7 +452,7 @@ font("ibm-plex-sans", { weights: [400, 600], italic: true }) // narrow/extend t
448
452
  // A project's own file: font("./fonts/Brand.ttf") → real family name read from the file itself
449
453
  // (weight/italic too — no options); two weight files of one family return the same name.
450
454
  // registerFont(family, url, {weight, style}) survives ONLY for runtime-computed URLs — rare.
451
- // No style inheritance — set fontFamily per UIText or via theme() (extract a shared Style<UIText>).
455
+ // The app's font: theme({ fontFamily }). A second family is set per UIText (extract a shared Style<UIText>).
452
456
 
453
457
  // ===== SVG IMAGES =====
454
458
  // SvgSource wraps raw SVG XML for use as an image source: UIImage(SvgSource(`<svg ...>`)).
@@ -550,17 +554,16 @@ screen.open()
550
554
  </file>
551
555
 
552
556
  // === EXAMPLE 2: Theme tokens + fetched list with loading / error states ===
553
- // A tokens module every screen imports, light-themed: theme({ color }) sets the default text
554
- // color ONCE — no color: "#111" on every label.
557
+ // A tokens module every screen imports, light-themed. ONE theme() call: the roles (bg, surface,
558
+ // border, text, textMuted, accent) color the screens, the default text and the tab bar — no
559
+ // color: "#111" on every label — and the app's own keys sit beside them.
555
560
  <file name="tokens.ts">
556
- const palette = {
557
- bg: "#F4F6F5", card: "#FFFFFF", border: "#E4E8E6",
558
- text: "#131A17", muted: "#606B65",
559
- accent: "#15A34A", accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
560
- }
561
- theme({ color: palette.text, primaryColor: palette.accent })
562
561
  // Accessors ARE "var(--x)" strings — re-calling theme() with new values restyles the live app.
563
- export const colors: { [K in keyof typeof palette]: string } = theme(palette)
562
+ export const colors = theme({
563
+ bg: "#F4F6F5", surface: "#FFFFFF", border: "#E4E8E6",
564
+ text: "#131A17", textMuted: "#606B65", accent: "#15A34A",
565
+ accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
566
+ })
564
567
  export const font = { // type scale — spread into styles: .style({ ...font.h2 })
565
568
  h2: { fontSize: 22, fontWeight: 700 }, bodyStrong: { fontSize: 16, fontWeight: 600 },
566
569
  small: { fontSize: 14 }, tiny: { fontSize: 12, fontWeight: 500 },
@@ -577,9 +580,9 @@ const Row = (u: User) => UIRow(
577
580
  justifyContent: "center", alignItems: "center" }),
578
581
  UIColumn(
579
582
  UIText(u.name).style({ ...font.bodyStrong }),
580
- UIText(u.email).style({ ...font.small, color: colors.muted }),
583
+ UIText(u.email).style({ ...font.small, color: colors.textMuted }),
581
584
  ).style({ flexGrow: 1, flexShrink: 1, gap: 2, alignItems: "flex-start" }),
582
- ).style({ alignItems: "center", gap: 12, bgColor: colors.card, borderRadius: 16,
585
+ ).style({ alignItems: "center", gap: 12, bgColor: colors.surface, borderRadius: 16,
583
586
  border: `1px solid ${colors.border}`, p: 14 })
584
587
 
585
588
  const Centered = (...children: UINodeChild[]) =>
@@ -588,7 +591,7 @@ const Centered = (...children: UINodeChild[]) =>
588
591
  let body: UIColumn
589
592
 
590
593
  const loadData = async () => {
591
- body.setContent([Centered(UIText("Loading…").style({ color: colors.muted }))])
594
+ body.setContent([Centered(UIText("Loading…").style({ color: colors.textMuted }))])
592
595
  const res = await fetch("https://jsonplaceholder.typicode.com/users")
593
596
  if (res.status !== 200) {
594
597
  body.setContent([Centered(
@@ -0,0 +1,146 @@
1
+ // The READER of the tween blob: `buildBlob`'s layout (./spec.ts) back into tracks and keys. For a
2
+ // host that plays an animation with no creator-tween in it (Web Lite hands the tracks to the
3
+ // browser's animations) — creator-tween reads the same layout in C (blob.h).
4
+ //
5
+ // The reader is the builder's twin and lives beside it: tests/animate/blob.test.ts builds a spec,
6
+ // reads it back and holds the two against each other, so a change of the layout that reaches one
7
+ // side only fails there.
8
+
9
+ import { EASE_TABLE, EASE_TABLE_SIZE, evalEasing, type EasingSpec } from "./easing"
10
+
11
+ /** The version of the layout this reader knows (./spec.ts BLOB_VERSION). */
12
+ const VERSION = 1
13
+ // The kinds whose one lane is an index into the string table (./spec.ts KIND_*).
14
+ const KIND_TRANSFORM_MATRIX = 4, KIND_DISCRETE = 8, KIND_STRING = 9
15
+
16
+ export type BlobKey = {
17
+ /** 0..1 within the track. */
18
+ t: number
19
+ easing: EasingSpec
20
+ /** Absent = the target's value when the animation first plays. */
21
+ value?: { lanes: number[] } | { str: string }
22
+ }
23
+
24
+ export type BlobTrack = {
25
+ domain: number
26
+ /** An index into the `targets` handed over next to the blob (a role, in a transition's blob). */
27
+ target: number
28
+ prop: string
29
+ kind: number
30
+ lanes: number
31
+ /** The unit of a FLOAT_UNIT, the function list of a TRANSFORM. */
32
+ sig?: string
33
+ atMs: number
34
+ durMs: number
35
+ commit: boolean
36
+ keys: BlobKey[]
37
+ }
38
+
39
+ export type ReadBlob = {
40
+ clock: number
41
+ /** One iteration, ms. */
42
+ durationMs: number
43
+ delayMs: number
44
+ /** -1 = forever. */
45
+ iterations: number
46
+ pingPong: boolean
47
+ rate: number
48
+ /** Time-callbacks, ms; the index is what the host reports. */
49
+ calls: number[]
50
+ tracks: BlobTrack[]
51
+ }
52
+
53
+ /** The blob as tracks, or null for data that is not a blob of this version (or is cut short). */
54
+ export const readBlob = (data: ArrayLike<number>, strings: readonly string[]): ReadBlob | null => {
55
+ if (data.length < 10 || data[0] !== VERSION) return null
56
+ const nTracks = data[7]!, nCalls = data[8]!, nTables = data[9]!
57
+ let at = 10
58
+ if (at + nCalls + nTables * EASE_TABLE_SIZE > data.length) return null
59
+ const calls: number[] = []
60
+ for (let i = 0; i < nCalls; i++) calls.push(data[at++]!)
61
+ const tables: Float32Array[] = []
62
+ for (let i = 0; i < nTables; i++) {
63
+ const table = new Float32Array(EASE_TABLE_SIZE)
64
+ for (let k = 0; k < EASE_TABLE_SIZE; k++) table[k] = data[at++]!
65
+ tables.push(table)
66
+ }
67
+ const str = (index: number): string | undefined => (index >= 0 ? strings[index] : undefined)
68
+
69
+ const tracks: BlobTrack[] = []
70
+ for (let t = 0; t < nTracks; t++) {
71
+ if (at + 10 > data.length) return null
72
+ const kind = data[at + 3]!, lanes = data[at + 4]!, nKeys = data[at + 8]!
73
+ const track: BlobTrack = {
74
+ domain: data[at]!, target: data[at + 1]!, prop: str(data[at + 2]!) ?? "", kind, lanes,
75
+ sig: str(data[at + 9]!), atMs: data[at + 5]!, durMs: data[at + 6]!, commit: data[at + 7]! !== 0, keys: [],
76
+ }
77
+ at += 10
78
+ if (at + nKeys * (7 + lanes) > data.length) return null
79
+ const stringLane = kind === KIND_TRANSFORM_MATRIX || kind === KIND_DISCRETE || kind === KIND_STRING
80
+ for (let k = 0; k < nKeys; k++) {
81
+ const easeKind = data[at + 1]!
82
+ const p: [number, number, number, number] = [data[at + 2]!, data[at + 3]!, data[at + 4]!, data[at + 5]!]
83
+ const easing: EasingSpec = easeKind === EASE_TABLE ? { kind: easeKind, p, table: tables[p[0]] } : { kind: easeKind, p }
84
+ const key: BlobKey = { t: data[at]!, easing }
85
+ if (data[at + 6]! !== 0) {
86
+ if (stringLane) key.value = { str: str(data[at + 7]!) ?? "" }
87
+ else {
88
+ const values: number[] = []
89
+ for (let l = 0; l < lanes; l++) values.push(data[at + 7 + l]!)
90
+ key.value = { lanes: values }
91
+ }
92
+ }
93
+ at += 7 + lanes
94
+ track.keys.push(key)
95
+ }
96
+ tracks.push(track)
97
+ }
98
+ if (at !== data.length) return null
99
+ return { clock: data[1]!, durationMs: data[2]!, delayMs: data[3]!, iterations: data[4]!, pingPong: data[5]! !== 0, rate: data[6]!, calls, tracks }
100
+ }
101
+
102
+ // ---- a track's value at a time -------------------------------------------------------------------
103
+
104
+ const KIND_QUAT = 6, KIND_MAT4 = 11
105
+
106
+ const slerp = (out: number[], o: number, a: number[], b: number[], s: number): void => {
107
+ let bx = b[o] ?? 0, by = b[o + 1] ?? 0, bz = b[o + 2] ?? 0, bw = b[o + 3] ?? 1
108
+ const ax = a[o] ?? 0, ay = a[o + 1] ?? 0, az = a[o + 2] ?? 0, aw = a[o + 3] ?? 1
109
+ let cosom = ax * bx + ay * by + az * bz + aw * bw
110
+ if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw }
111
+ let k0 = 1 - s, k1 = s
112
+ if (1 - cosom > 1e-6) {
113
+ const omega = Math.acos(Math.min(1, cosom)), sinom = Math.sin(omega)
114
+ k0 = Math.sin((1 - s) * omega) / sinom
115
+ k1 = Math.sin(s * omega) / sinom
116
+ }
117
+ out[o] = k0 * ax + k1 * bx; out[o + 1] = k0 * ay + k1 * by; out[o + 2] = k0 * az + k1 * bz; out[o + 3] = k0 * aw + k1 * bw
118
+ }
119
+
120
+ /** The two keys around the local time `ms` of a track and how far between them the track is,
121
+ * eased: the first key holds before the track, the last one after it. */
122
+ export const segmentAt = (track: BlobTrack, ms: number): { from: BlobKey, to: BlobKey, s: number } | null => {
123
+ const keys = track.keys
124
+ if (keys.length === 0) return null
125
+ const u = track.durMs <= 0 ? (ms >= track.atMs ? 1 : 0) : Math.min(1, Math.max(0, (ms - track.atMs) / track.durMs))
126
+ const first = keys[0]!, last = keys[keys.length - 1]!
127
+ if (keys.length === 1 || u <= first.t) return { from: first, to: first, s: 0 }
128
+ if (u >= last.t) return { from: last, to: last, s: 0 }
129
+ let i = 0
130
+ while (i + 1 < keys.length && keys[i + 1]!.t <= u) i++
131
+ const from = keys[i]!, to = keys[i + 1]!
132
+ const span = to.t - from.t
133
+ return { from, to, s: evalEasing(from.easing, span > 0 ? (u - from.t) / span : 1) }
134
+ }
135
+
136
+ /** The lanes of a numeric track at the local time `ms`, into `out` (a key with no value reads as zeros). */
137
+ export const sampleTrack = (track: BlobTrack, ms: number, out: number[]): void => {
138
+ const at = segmentAt(track, ms)
139
+ if (!at) return
140
+ const lanesOf = (key: BlobKey): number[] => (key.value && "lanes" in key.value ? key.value.lanes : [])
141
+ const a = lanesOf(at.from), b = lanesOf(at.to), s = at.s
142
+ const lerp = (from: number, to: number) => { for (let k = from; k < to; k++) out[k] = (a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * s }
143
+ if (track.kind === KIND_QUAT) { slerp(out, 0, a, b, s); return }
144
+ if (track.kind === KIND_MAT4) { lerp(0, 3); slerp(out, 3, a, b, s); lerp(7, 10); return }
145
+ lerp(0, track.lanes)
146
+ }
@@ -28,6 +28,15 @@ declare global {
28
28
  /** Fire a one-shot semantic haptic (device.vibrate). `style` is a HapticStyle string. Optional:
29
29
  * hosts without haptic hardware (web, headless, iPad / older iPhones) simply don't implement it. */
30
30
  vibrate?(style: string): void
31
+ /** The HOST's performance overlay (device.statsOverlay): frames per second as the display got
32
+ * them, how evenly, the GPU's and the main thread's milliseconds — a line the host draws over
33
+ * everything itself, about once a second, with no JS, tree or layout behind it. It is the
34
+ * host's state, not a world's: it stays through `_creatorApp.run()`, so a launcher turns it
35
+ * on for whatever it opens next. Optional, as a pair: a host with no overlay (web, headless)
36
+ * leaves both out and the SDK reads `false`. */
37
+ setStatsOverlay?(enabled: boolean): void
38
+ /** Whether the overlay is on. */
39
+ statsOverlay?(): boolean
31
40
 
32
41
  // ---- device.motion (orientation sensor; pull-per-frame model) --------------------------------
33
42
  /** Does this device have the motion sensors (gyro)? Surfaced as device.motion.available. */
@@ -67,6 +67,11 @@ declare global {
67
67
  // Theme variables: MERGES `values` (null removes a key, numbers are px lengths, a color is a
68
68
  // string) and re-resolves live styles. Env names (safe-*, vw/vh/…) are reserved.
69
69
  setTheme(values: Record<string, string | f64 | null>): void
70
+ // The same for ONE node's subtree (`el.theme({...})`): a SCOPE. A `var(--name)` on a node reads
71
+ // the nearest scope up its tree — the node, its ancestors, and for a pager's page or a vlist's
72
+ // item the pager / list it is in — then the app's table. A destination pushed over the node and
73
+ // a widget are roots of their own: no scope reaches them. The comfort-* knobs stay app-wide.
74
+ setNodeTheme(id: TreeNodeId, values: Record<string, string | f64 | null>): void
70
75
 
71
76
  // ---- presentation -------------------------------------------------------------------------
72
77
  // A transition (sdk/src/ui/transitions.ts): a tween blob whose targets are ROLES — `roles[i]` is
@@ -1,4 +1,4 @@
1
- // GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/cssColor.ts @ ae6bc30b952b — do not edit here.
1
+ // GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/cssColor.ts @ 72e545dab1f5 — do not edit here.
2
2
  // Bump the submodule and run `bun run vendor:canvas`; `bun run check:canvas-ops` fails when this copy is stale.
3
3
  // The CSS color parser — the TypeScript twin of core/include/anycanvas/css_color.h: the same grammar,
4
4
  // the same name table, float32 emulated op for op (Math.fround, in the header's order), so both give
@@ -1,4 +1,4 @@
1
- // GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/recorder.ts @ ae6bc30b952b — do not edit here.
1
+ // GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/recorder.ts @ 72e545dab1f5 — do not edit here.
2
2
  // Bump the submodule and run `bun run vendor:canvas`; `bun run check:canvas-ops` fails when this copy is stale.
3
3
  // The TypeScript recorder: a Canvas2D-shaped API whose calls WRITE the opcode stream of spec/ops.h.
4
4
  // Nothing is drawn here — the stream goes to the core interpreter (native, or wasm on the web), which
@@ -1,4 +1,4 @@
1
- // GENERATED by scripts/vendor-canvas.ts from engines/canvas/spec/gen/spec.ts @ ae6bc30b952b — do not edit here.
1
+ // GENERATED by scripts/vendor-canvas.ts from engines/canvas/spec/gen/spec.ts @ 72e545dab1f5 — do not edit here.
2
2
  // Bump the submodule and run `bun run vendor:canvas`; `bun run check:canvas-ops` fails when this copy is stale.
3
3
  // GENERATED by spec/generate.ts from spec/*.h — do not edit.
4
4
 
package/src/chisel.ts CHANGED
@@ -123,7 +123,7 @@ export const chiselVocab: ChiselVocab = {
123
123
  // Exactly the builder methods that return the node (tests/compile/chisel-vocab.test.ts proves it against
124
124
  // the interfaces): `animateTo` / `animateFrom` return an Animation, so they are NOT here.
125
125
  chain: [
126
- "style", "class", "named",
126
+ "style", "class", "named", "theme",
127
127
  "onClick", "onTouchStart", "onLongPress", "onMouseEnter", "onLayout", "onOpen", "onClose", "onBack",
128
128
  "onScroll", "onScrollRelease", "onOverscroll", "onRefresh", "onSubmit",
129
129
  "onSelect", "onChange", "onFocus", "onBlur", "onOverlayTap", "onDetent",
@@ -69,6 +69,12 @@ export const SERVER_INJECT = "/__sdk/server/inject.ts"
69
69
  * contract, `__sceneEditor` (src/inject.editor.ts → src/scene/editor.ts). Never in a shipped bundle. */
70
70
  export const EDITOR_INJECT = "/__sdk/inject.editor.ts"
71
71
 
72
+ /** The text of the two inject modules — the lists of what is a global on each side (serverTypes.ts). */
73
+ export const readInjectSources = (): { client: string, server: string } => {
74
+ const client = resolveInject()
75
+ return { client: readFileSync(client, "utf8"), server: readFileSync(join(dirname(client), "server", "inject.ts"), "utf8") }
76
+ }
77
+
72
78
  /** The SDK source root (parent of `inject.ts`). chisel needs the source tree, not a prebuilt bundle. */
73
79
  const resolveSdkSrc = (): string => {
74
80
  const inject = resolveInject()