@typecad/ui 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (135) hide show
  1. package/README.md +21 -1
  2. package/assets/fonts/dejavu/DejaVuSans-Bold.ttf +0 -0
  3. package/assets/fonts/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  4. package/assets/fonts/dejavu/DejaVuSans-Oblique.ttf +0 -0
  5. package/assets/fonts/dejavu/DejaVuSans.ttf +0 -0
  6. package/assets/fonts/dejavu/DejaVuSansMono-Bold.ttf +0 -0
  7. package/assets/fonts/dejavu/DejaVuSansMono.ttf +0 -0
  8. package/assets/fonts/dejavu/LICENSE +187 -0
  9. package/assets/fonts/dejavu/README.md +49 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +74 -0
  12. package/dist/engine-index.js +2 -0
  13. package/dist/preview/build-program.d.ts +7 -1
  14. package/dist/preview/build-program.js +94 -15
  15. package/dist/preview/host-gfx.d.ts +3 -0
  16. package/dist/preview/host-gfx.js +30 -3
  17. package/dist/preview/host-ui-runtime.d.ts +115 -0
  18. package/dist/preview/host-ui-runtime.js +928 -46
  19. package/dist/ui-engine/block-layout.js +25 -11
  20. package/dist/ui-engine/color.d.ts +6 -0
  21. package/dist/ui-engine/color.js +80 -2
  22. package/dist/ui-engine/compat-report.d.ts +8 -0
  23. package/dist/ui-engine/compat-report.js +124 -0
  24. package/dist/ui-engine/css-imports.d.ts +4 -0
  25. package/dist/ui-engine/css-imports.js +70 -0
  26. package/dist/ui-engine/css-parser.js +58 -14
  27. package/dist/ui-engine/default-font.d.ts +25 -0
  28. package/dist/ui-engine/default-font.js +80 -0
  29. package/dist/ui-engine/font-assets.d.ts +2 -2
  30. package/dist/ui-engine/font-assets.js +22 -5
  31. package/dist/ui-engine/html-parser.d.ts +4 -0
  32. package/dist/ui-engine/html-parser.js +241 -26
  33. package/dist/ui-engine/image-assets.d.ts +8 -1
  34. package/dist/ui-engine/image-assets.js +35 -2
  35. package/dist/ui-engine/image-decode.d.ts +22 -0
  36. package/dist/ui-engine/image-decode.js +194 -0
  37. package/dist/ui-engine/inline-parser.js +1 -1
  38. package/dist/ui-engine/layout-engine.d.ts +19 -0
  39. package/dist/ui-engine/layout-engine.js +59 -3
  40. package/dist/ui-engine/model.d.ts +18 -0
  41. package/dist/ui-engine/model.js +76 -8
  42. package/dist/ui-engine/runtime-header/blend-bodies.js +29 -9
  43. package/dist/ui-engine/runtime-header/canvas-helpers.js +5 -2
  44. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -111
  45. package/dist/ui-engine/runtime-header/cuttlefish-gfx.js +21 -0
  46. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -179
  47. package/dist/ui-engine/runtime-header/forward-decls.js +215 -167
  48. package/dist/ui-engine/runtime-header/init-press-input.js +138 -135
  49. package/dist/ui-engine/runtime-header/keyboard.js +682 -380
  50. package/dist/ui-engine/runtime-header/node-draw-body.js +1656 -1377
  51. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -214
  52. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -568
  53. package/dist/ui-engine/runtime-header/scroll-physics.js +25 -1
  54. package/dist/ui-engine/runtime-header/state-bindings-nav.js +9 -0
  55. package/dist/ui-engine/runtime-header/structs.js +228 -216
  56. package/dist/ui-engine/runtime-header/text-rendering.js +888 -770
  57. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -116
  58. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +897 -633
  59. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -25
  60. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +79 -23
  61. package/dist/ui-engine/runtime-header/types-defines.js +110 -93
  62. package/dist/ui-engine/shadcn-kit.d.ts +1 -0
  63. package/dist/ui-engine/shadcn-kit.js +502 -0
  64. package/dist/ui-engine/style-resolver.d.ts +4 -0
  65. package/dist/ui-engine/style-resolver.js +58 -6
  66. package/dist/ui-engine/transpile-ui.js +12 -5
  67. package/dist/ui-engine/ua-stylesheet.d.ts +22 -1
  68. package/dist/ui-engine/ua-stylesheet.js +119 -32
  69. package/dist/ui-engine/ui-lowering.js +37 -3
  70. package/dist/ui-engine/ui-registry.js +32 -4
  71. package/dist/ui-engine/yoga-layout.js +57 -52
  72. package/dist/wizard/config-writer.d.ts +49 -0
  73. package/dist/wizard/config-writer.js +366 -0
  74. package/dist/wizard/display-catalog.d.ts +75 -0
  75. package/dist/wizard/display-catalog.js +207 -0
  76. package/dist/wizard/index.d.ts +7 -0
  77. package/dist/wizard/index.js +10 -0
  78. package/dist/wizard/integration-wizard.d.ts +15 -0
  79. package/dist/wizard/integration-wizard.js +486 -0
  80. package/dist/wizard/prompts.d.ts +32 -0
  81. package/dist/wizard/prompts.js +95 -0
  82. package/dist/wizard/starter-ui.d.ts +1 -0
  83. package/dist/wizard/starter-ui.js +56 -0
  84. package/package.json +18 -5
  85. package/src/cli.ts +87 -0
  86. package/src/engine-index.ts +53 -51
  87. package/src/preview/build-program.ts +810 -734
  88. package/src/preview/host-gfx.ts +30 -3
  89. package/src/preview/host-ui-runtime.ts +4133 -3289
  90. package/src/ui-engine/block-layout.ts +24 -11
  91. package/src/ui-engine/color.ts +77 -2
  92. package/src/ui-engine/compat-report.ts +139 -0
  93. package/src/ui-engine/css-imports.ts +69 -0
  94. package/src/ui-engine/css-parser.ts +64 -15
  95. package/src/ui-engine/default-font.ts +95 -0
  96. package/src/ui-engine/font-assets.ts +545 -525
  97. package/src/ui-engine/html-parser.ts +615 -397
  98. package/src/ui-engine/image-assets.ts +37 -2
  99. package/src/ui-engine/image-decode.ts +244 -0
  100. package/src/ui-engine/inline-parser.ts +1 -1
  101. package/src/ui-engine/layout-engine.ts +61 -3
  102. package/src/ui-engine/model.ts +1317 -1230
  103. package/src/ui-engine/runtime-header/blend-bodies.ts +29 -9
  104. package/src/ui-engine/runtime-header/canvas-helpers.ts +5 -2
  105. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -117
  106. package/src/ui-engine/runtime-header/cuttlefish-gfx.ts +21 -0
  107. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -185
  108. package/src/ui-engine/runtime-header/forward-decls.ts +227 -179
  109. package/src/ui-engine/runtime-header/init-press-input.ts +144 -141
  110. package/src/ui-engine/runtime-header/keyboard.ts +689 -386
  111. package/src/ui-engine/runtime-header/node-draw-body.ts +1681 -1402
  112. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -220
  113. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -574
  114. package/src/ui-engine/runtime-header/scroll-physics.ts +25 -1
  115. package/src/ui-engine/runtime-header/state-bindings-nav.ts +9 -0
  116. package/src/ui-engine/runtime-header/structs.ts +234 -222
  117. package/src/ui-engine/runtime-header/text-rendering.ts +894 -776
  118. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -122
  119. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -638
  120. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -31
  121. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +79 -23
  122. package/src/ui-engine/runtime-header/types-defines.ts +116 -99
  123. package/src/ui-engine/shadcn-kit.ts +503 -0
  124. package/src/ui-engine/style-resolver.ts +471 -416
  125. package/src/ui-engine/transpile-ui.ts +12 -5
  126. package/src/ui-engine/ua-stylesheet.ts +137 -31
  127. package/src/ui-engine/ui-lowering.ts +486 -452
  128. package/src/ui-engine/ui-registry.ts +556 -524
  129. package/src/ui-engine/yoga-layout.ts +307 -309
  130. package/src/wizard/config-writer.ts +404 -0
  131. package/src/wizard/display-catalog.ts +273 -0
  132. package/src/wizard/index.ts +38 -0
  133. package/src/wizard/integration-wizard.ts +619 -0
  134. package/src/wizard/prompts.ts +145 -0
  135. package/src/wizard/starter-ui.ts +58 -0
@@ -1,31 +1,30 @@
1
- // Slice of the C++ runtime header (original source lines 5279-5287).
2
- // scrollbar + buffered scroll canvas push.
3
- // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
- export function emitTickScrollCanvasPhase(): string {
5
- return `
6
- }
7
- // ②b Draw scrollbar + push canvas for the buffered scroll container.
8
- if (bufferedScrollNode >= 0 && bufferedScrollCanvas) {
9
- ui_push_buffered_scroll_canvas(bufferedScrollCanvas, bufferedScrollRepaintCanvas,
10
- bufferedScrollNode, bufferedScrollVX, bufferedScrollVY,
11
- bufferedScrollRepaintY, bufferedScrollRepaintH, __ui_draw_target);
12
- } else if (bufferedScrollNode >= 0 && bufferedScrollBands) {
13
- // Band render fallback: the main loop didn't trigger it at the owner's
14
- // z-slot (e.g. the owner was filtered out after dispatch). Render now so
15
- // the subtree is never left unpainted. ui_render_scroll_bands sets
16
- // lastPaintedScrollY itself on success; only the failure fallback (band
17
- // canvas wouldn't allocate) needs to record the scrollY here so the next
18
- // frame's delta is correct.
19
- if (!ui_render_scroll_bands(static_cast<uint16_t>(bufferedScrollNode))) {
20
- // No band canvas means there is no coherent frame to submit. Retain the
21
- // previous viewport rather than drawing the scrollbar/direct subtree to
22
- // the live SPI target, which would reintroduce tearing.
23
- if (__ui_scroll_render_locked) __ui_scroll_render_locked[static_cast<uint16_t>(bufferedScrollNode)] = 1;
24
- __ui_nodes[bufferedScrollNode].dirty = 0;
25
- }
26
- } else if (bufferedScrollNode >= 0 && bufferedScrollDirectFull) {
27
- // The direct-full path is intentionally disabled for tear-sensitive scroll
28
- // rendering. Keep the retained pixels when no compositor is available.
29
- if (__ui_scroll_render_locked) __ui_scroll_render_locked[static_cast<uint16_t>(bufferedScrollNode)] = 1;
30
- __ui_nodes[bufferedScrollNode].dirty = 0;`;
31
- }
1
+ // Slice of the C++ runtime header (original source lines 5279-5287).
2
+ // scrollbar + buffered scroll canvas push.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitTickScrollCanvasPhase(): string {
5
+ return `
6
+ // ②b Draw scrollbar + push canvas for the buffered scroll container.
7
+ if (bufferedScrollNode >= 0 && bufferedScrollCanvas) {
8
+ ui_push_buffered_scroll_canvas(bufferedScrollCanvas, bufferedScrollRepaintCanvas,
9
+ bufferedScrollNode, bufferedScrollVX, bufferedScrollVY,
10
+ bufferedScrollRepaintY, bufferedScrollRepaintH, __ui_draw_target);
11
+ } else if (bufferedScrollNode >= 0 && bufferedScrollBands) {
12
+ // Band render fallback: the main loop didn't trigger it at the owner's
13
+ // z-slot (e.g. the owner was filtered out after dispatch). Render now so
14
+ // the subtree is never left unpainted. ui_render_scroll_bands sets
15
+ // lastPaintedScrollY itself on success; only the failure fallback (band
16
+ // canvas wouldn't allocate) needs to record the scrollY here so the next
17
+ // frame's delta is correct.
18
+ if (!ui_render_scroll_bands(static_cast<uint16_t>(bufferedScrollNode))) {
19
+ // No band canvas means there is no coherent frame to submit. Retain the
20
+ // previous viewport rather than drawing the scrollbar/direct subtree to
21
+ // the live SPI target, which would reintroduce tearing.
22
+ if (__ui_scroll_render_locked) __ui_scroll_render_locked[static_cast<uint16_t>(bufferedScrollNode)] = 1;
23
+ __ui_nodes[bufferedScrollNode].dirty = 0;
24
+ }
25
+ } else if (bufferedScrollNode >= 0 && bufferedScrollDirectFull) {
26
+ // The direct-full path is intentionally disabled for tear-sensitive scroll
27
+ // rendering. Keep the retained pixels when no compositor is available.
28
+ if (__ui_scroll_render_locked) __ui_scroll_render_locked[static_cast<uint16_t>(bufferedScrollNode)] = 1;
29
+ __ui_nodes[bufferedScrollNode].dirty = 0;`;
30
+ }
@@ -3,6 +3,7 @@
3
3
  // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
4
  export function emitTouchKeyboardFwd(): string {
5
5
  return `
6
+ #include <cstdlib> // abs() for drag-distance thresholds (self-sufficient slice)
6
7
  // ── Touch hit-testing + click dispatch ─────────────────────────────────────
7
8
  // Radio groups for mutual exclusion
8
9
  struct UIRadioGroup {
@@ -13,11 +14,14 @@ extern UIRadioGroup __ui_radio_groups[];
13
14
  extern const uint16_t __ui_radio_group_count;
14
15
 
15
16
  // Forward-declare the click handler type + tables (defined by the emit layer).
16
- extern void (*__ui_click_handlers[])();
17
- extern void (*__ui_hold_handlers[])();
18
- extern void (*__ui_release_handlers[])();
17
+ // The tables are const (flash rodata) — they are never written at runtime.
18
+ extern void (*const __ui_click_handlers[])();
19
+ extern void (*const __ui_hold_handlers[])();
20
+ extern void (*const __ui_release_handlers[])();
19
21
  extern void (*__ui_rangechange_handlers[])();
20
22
  extern const uint16_t __ui_click_handler_count;
23
+ extern const uint16_t __ui_hold_handler_count;
24
+ extern const uint16_t __ui_release_handler_count;
21
25
  extern const uint16_t __ui_rangechange_handler_count;
22
26
 
23
27
  // Touch state machine: tracks down → hold → up → click lifecycle
@@ -77,6 +81,10 @@ static inline void ui_kb_handle_touch(int16_t tx, int16_t ty);
77
81
  static inline void ui_kb_handle_tap(int16_t tx, int16_t ty);
78
82
  static inline void ui_kb_key_rect(uint8_t idx, UIRect* out);
79
83
  static inline void ui_kb_draw();
84
+
85
+ // Modal <select> + <drawer> declarations live in forward-decls.ts
86
+ // (emit order: ui_navigate/visibility/ui_init reference them before this module).
87
+
80
88
  static inline void ui_kb_draw_key(uint8_t i);
81
89
  static inline void ui_kb_draw_text_row();
82
90
  static inline void ui_kb_compute_box();
@@ -85,13 +93,22 @@ static inline void ui_kb_compute_box();
85
93
  #define UI_DRAG_THRESHOLD 10
86
94
 
87
95
  // Hit-test a touch point against all visible nodes (topmost first).
88
- // Returns the node index of the topmost node that BOTH contains the point
89
- // AND has a click handler registered. Returns -1 if none.
96
+ // Returns the node the tap TARGETS, resolved like the DOM: the topmost node
97
+ // whose box contains the point, then bubbling to ANCESTORS only. Returns -1
98
+ // if no ancestor of the target is interactive or handler-bearing.
90
99
  static int16_t ui_hit_test(int16_t tx, int16_t ty) {
91
- int16_t best = -1;
100
+ // Phase 1 — the event target: the TOPMOST node containing the tap (last in
101
+ // draw order), regardless of handlers. Filtering for handler-bearing nodes
102
+ // here let a tap fall through SIBLING layers: a modal card (no handler,
103
+ // higher z) over a click-to-close scrim (handler) routed every card tap to
104
+ // the scrim underneath, so any tap dismissed the dialog. In the DOM the
105
+ // click targets the topmost element and never propagates sideways to a
106
+ // covered sibling.
107
+ int16_t target = -1;
92
108
  for (uint16_t i = 0; i < __ui_node_count; i++) {
93
109
  if (!ui_is_effectively_visible(i)) continue;
94
110
  if (__ui_nodes[i].screenId != __ui_active_screen) continue;
111
+ if (__ui_nodes[i].disabled) continue; // HTML disabled: not a tap target
95
112
  int16_t drawX = ui_draw_x_for_node(static_cast<uint16_t>(i));
96
113
  int16_t drawY = ui_draw_y_for_node(static_cast<uint16_t>(i));
97
114
  if (tx >= drawX && tx < drawX + __ui_nodes[i].box.w &&
@@ -101,26 +118,29 @@ static int16_t ui_hit_test(int16_t tx, int16_t ty) {
101
118
  // a tall wrapped paragraph can overflow below the fold yet still have a
102
119
  // tappable link segment in its visible portion.
103
120
  if (ui_is_point_clipped_by_scroll(static_cast<uint16_t>(i), tx, ty)) continue;
104
- // Skip nodes without any click handler they're containers, not targets.
105
- // Exceptions: NODE_RANGE (horizontal drag), NODE_INPUT (opens keyboard),
106
- // NODE_LIST (virtualized item tap), and NODE_BUTTON — the last so a button
107
- // gets :pressed/transition visual feedback even with no JS onClick wired
108
- // (a pure-CSS button like a demo "tap to transition" control).
109
- if (__ui_nodes[i].kind == NODE_RANGE || __ui_nodes[i].kind == NODE_INPUT || __ui_nodes[i].kind == NODE_LIST || __ui_nodes[i].kind == NODE_BUTTON || __ui_nodes[i].kind == NODE_CHECK || __ui_nodes[i].kind == NODE_RADIO) {
110
- if (best < 0 || ui_node_draws_before(best, i)) best = i;
111
- continue;
112
- }
113
- if (i < __ui_click_handler_count &&
114
- (__ui_click_handlers[i] || __ui_hold_handlers[i] || __ui_release_handlers[i])) {
115
- if (best < 0 || ui_node_draws_before(best, i)) best = i;
116
- }
121
+ if (target < 0 || ui_node_draws_before(target, i)) target = static_cast<int16_t>(i);
117
122
  }
118
123
  }
119
- return best;
124
+ // Phase 2 — bubbling: walk from the target up through its ancestors.
125
+ // Interactive kinds match even with no JS handler wired — NODE_RANGE
126
+ // (horizontal drag), NODE_INPUT (opens the keyboard), NODE_LIST
127
+ // (virtualized item tap), and NODE_BUTTON/NODE_CHECK/NODE_RADIO so a
128
+ // pure-CSS control still gets :pressed/transition visual feedback.
129
+ int16_t n = target;
130
+ while (n >= 0) {
131
+ uint16_t u = static_cast<uint16_t>(n);
132
+ if (__ui_nodes[u].kind == NODE_RANGE || __ui_nodes[u].kind == NODE_INPUT || __ui_nodes[u].kind == NODE_LIST || __ui_nodes[u].kind == NODE_BUTTON || __ui_nodes[u].kind == NODE_CHECK || __ui_nodes[u].kind == NODE_RADIO) return n;
133
+ if ((u < __ui_click_handler_count && __ui_click_handlers[u]) ||
134
+ (u < __ui_hold_handler_count && __ui_hold_handlers[u]) ||
135
+ (u < __ui_release_handler_count && __ui_release_handlers[u])) return n;
136
+ if (__ui_nodes[u].parent == UI_NO_PARENT || __ui_nodes[u].parent >= __ui_node_count) break;
137
+ n = static_cast<int16_t>(__ui_nodes[u].parent);
138
+ }
139
+ return -1;
120
140
  }
121
141
 
122
142
  // Dispatch a handler from the given table if registered for the node.
123
- static void ui_dispatch(void (**table)(), uint16_t count, int16_t node) {
143
+ static void ui_dispatch(void (*const* table)(), uint16_t count, int16_t node) {
124
144
  if (node >= 0 && static_cast<uint16_t>(node) < count && table[node]) {
125
145
  table[node]();
126
146
  }
@@ -150,6 +170,13 @@ static inline void ui_open_keyboard_for_input(uint16_t nodeIdx) {
150
170
 
151
171
  // Touch down: called when screen is first touched.
152
172
  static void ui_touch_down(int16_t tx, int16_t ty) {
173
+ // Modal <select> list: record the touch position; tap-up routes to the
174
+ // option rows. No hit-test/press/scroll while the overlay is open.
175
+ if (__ui_select_menu >= 0) {
176
+ __ui_touch_state = 1;
177
+ __ui_touch_down_time = millis();
178
+ return;
179
+ }
153
180
  int16_t node = ui_hit_test(tx, ty);
154
181
  __ui_touch_node = node;
155
182
  __ui_touch_state = 1;
@@ -208,6 +235,35 @@ static void ui_touch_up() {
208
235
  // Modal keyboard: route tap-up to the keyboard; swallow normal click logic.
209
236
  // (Skipped on UI_HIDE_OSK desktop targets — touches pass through to the app,
210
237
  // since the editing session is driven by the real keyboard, not the grid.)
238
+ // Open drawers close on outside taps (device parity with the preview).
239
+ {
240
+ int16_t dx0 = __ui_last_touch_x;
241
+ int16_t dy0 = __ui_last_touch_y;
242
+ for (uint8_t s = 0; s < __ui_drawer_slots; s++) {
243
+ if (__ui_drawer_idx[s] < 0 || !__ui_drawer_open[s] || __ui_drawer_progress[s] < 255) continue;
244
+ uint16_t di = static_cast<uint16_t>(__ui_drawer_idx[s]);
245
+ // Hit-test the SUBTREE rect: a <dialog> root with absolutely
246
+ // positioned children lays out at h=0 — the root box made every tap
247
+ // count as "outside" and closed the dialog immediately.
248
+ UIRect panelRect;
249
+ if (!ui_subtree_current_paint_rect(di, &panelRect)) {
250
+ panelRect.x = ui_draw_x_for_node(di);
251
+ panelRect.y = ui_draw_y_for_node(di);
252
+ panelRect.w = __ui_nodes[di].box.w;
253
+ panelRect.h = __ui_nodes[di].box.h;
254
+ }
255
+ if (dx0 < panelRect.x || dx0 >= panelRect.x + panelRect.w ||
256
+ dy0 < panelRect.y || dy0 >= panelRect.y + panelRect.h) {
257
+ __ui_drawer_open[s] = 0;
258
+ }
259
+ }
260
+ }
261
+ if (__ui_select_menu >= 0) {
262
+ ui_select_menu_tap(__ui_last_touch_x, __ui_last_touch_y);
263
+ __ui_touch_state = 0;
264
+ __ui_last_touch_time = millis();
265
+ return;
266
+ }
211
267
  #if !defined(UI_HIDE_OSK)
212
268
  if (__ui_kb_visible) {
213
269
  ui_kb_handle_tap(__ui_last_touch_x, __ui_last_touch_y);
@@ -241,7 +297,7 @@ static void ui_touch_up() {
241
297
  if (target >= 0) ui_navigate(static_cast<uint8_t>(target));
242
298
  }
243
299
  }
244
- ui_dispatch(__ui_release_handlers, __ui_click_handler_count, __ui_touch_node);
300
+ ui_dispatch(__ui_release_handlers, __ui_release_handler_count, __ui_touch_node);
245
301
  // Built-in check/radio/select handlers mutate the node value during click
246
302
  // dispatch and need one repaint. Buttons are already dirty from releasing
247
303
  // :pressed; links, lists, inputs, and ordinary callbacks are either handled
@@ -389,7 +445,7 @@ static inline void ui_handle_touch(int16_t tx, int16_t ty) {
389
445
  if (__ui_touch_state == 1 && __ui_touch_node >= 0 && !__ui_is_dragging) {
390
446
  if (now - __ui_touch_down_time >= UI_TOUCH_HOLD_MS) {
391
447
  __ui_touch_state = 2;
392
- ui_dispatch(__ui_hold_handlers, __ui_click_handler_count, __ui_touch_node);
448
+ ui_dispatch(__ui_hold_handlers, __ui_hold_handler_count, __ui_touch_node);
393
449
  }
394
450
  }
395
451
  }
@@ -1,99 +1,116 @@
1
- // Slice of the C++ runtime header (original source lines 17-89).
2
- // Guard open, includes, and #define knobs.
3
- // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
- export function emitTypesDefines(): string {
5
- return `
6
- // ── Cuttlefish UI runtime (emit once per TU) ──────────────────────────────
7
- #ifndef __TC_UI_RUNTIME
8
- #define __TC_UI_RUNTIME
9
- #include <stdint.h>
10
- #include <cstring>
11
- #include <new>
12
- #define UI_TEXT_BUF 32 // max stored UI text chars, excluding the trailing NUL
13
- #define UI_TEXT_LINE_BUF 96
14
- #define UI_WS_NORMAL 0
15
- #define UI_WS_NOWRAP 1
16
- #define UI_WS_PRE 2
17
- #define UI_WS_PRE_LINE 3
18
- #ifndef UI_MAX_BUFFERED_PAINT_PIXELS
19
- #define UI_MAX_BUFFERED_PAINT_PIXELS 20000
20
- #endif
21
- // Above this many pixels, prefer the band renderer (small ~10KB SRAM canvas,
22
- // strip-at-a-time) over the repair canvas even when the repair canvas could
23
- // allocate (e.g. in PSRAM). A large repair canvas (a full-width button is
24
- // ~15-30KB) has higher alloc/seed/push latency than banding — the per-band
25
- // push is smaller and the band canvas lives in fast internal SRAM. Set below
26
- // UI_MAX_BUFFERED_PAINT_PIXELS so large rects band while small repaints keep
27
- // the fast single-canvas composite. Tune per target; 0 = always prefer the
28
- // repair canvas when it allocates (the pre-PSRAM behavior).
29
- #ifndef UI_BAND_PREFER_PIXELS
30
- #define UI_BAND_PREFER_PIXELS 8000
31
- #endif
32
- #ifndef UI_USE_FULL_FRAMEBUFFER
33
- #define UI_USE_FULL_FRAMEBUFFER 0
34
- #endif
35
- #ifndef UI_TRANSITION_SNAP_MS
36
- #define UI_TRANSITION_SNAP_MS 100
37
- #endif
38
- // Scroll physics + capability defaults. The per-TU #define overrides emitted
39
- // by the UI emitter (from resolveScrollConfig) hit BEFORE this header, so these
40
- // #ifndef guards adopt the configured values. Spec: 2026-06-28-scroll-engine-rewrite-design.md
41
- #ifndef UI_SCROLL_MAX_OVERSCROLL
42
- #define UI_SCROLL_MAX_OVERSCROLL 40
43
- #endif
44
- #ifndef UI_SCROLL_STIFFNESS_X10
45
- #define UI_SCROLL_STIFFNESS_X10 5
46
- #endif
47
- #ifndef UI_SCROLL_EDGE_SNAP_PX
48
- #define UI_SCROLL_EDGE_SNAP_PX 12
49
- #endif
50
- #ifndef UI_SCROLL_DRAG_SCALE_X10
51
- #define UI_SCROLL_DRAG_SCALE_X10 10
52
- #endif
53
- #ifndef UI_SCROLL_SETTLE_MS
54
- #define UI_SCROLL_SETTLE_MS 180
55
- #endif
56
- #ifndef UI_SCROLL_DEADBAND_PX
57
- #define UI_SCROLL_DEADBAND_PX 2
58
- #endif
59
- // Capability tier flags (emitted per-TU before this header; defaults = full).
60
- #ifndef UI_SCROLL_INPUT_TIER_CAPACITIVE
61
- #define UI_SCROLL_INPUT_TIER_CAPACITIVE 0
62
- #endif
63
- #ifndef UI_SCROLL_INPUT_TIER_RESISTIVE
64
- #define UI_SCROLL_INPUT_TIER_RESISTIVE 0
65
- #endif
66
- #ifndef UI_SCROLL_INPUT_TIER_NONE
67
- #define UI_SCROLL_INPUT_TIER_NONE 0
68
- #endif
69
- #ifndef UI_SCROLL_RENDER_TIER_FULL
70
- #define UI_SCROLL_RENDER_TIER_FULL 1
71
- #endif
72
- #ifndef UI_SCROLL_RENDER_TIER_CONSTRAINED
73
- #define UI_SCROLL_RENDER_TIER_CONSTRAINED 0
74
- #endif
75
- #if (UI_SCROLL_INPUT_TIER_CAPACITIVE + UI_SCROLL_INPUT_TIER_RESISTIVE + UI_SCROLL_INPUT_TIER_NONE) == 0
76
- #define UI_SCROLL_INPUT_TIER_RESISTIVE 1
77
- #endif
78
- #define UI_SCROLL_HAS_TOUCH (UI_SCROLL_INPUT_TIER_CAPACITIVE || UI_SCROLL_INPUT_TIER_RESISTIVE)
79
- #define UI_SCROLL_ELASTIC (UI_SCROLL_RENDER_TIER_FULL)
80
- // Telemetry: emits per-frame scrollY/overscrollPx/dy over Serial when defined.
81
- #ifndef UI_SCROLL_DEBUG
82
- #define UI_SCROLL_DEBUG 0
83
- #endif
84
- // Band renderer: height (px) of the horizontal band canvas used to composite
85
- // scroll subtrees tear-free when no viewport canvas fits (no PSRAM / over
86
- // budget). Each band is vw × UI_STRIP_BAND_HEIGHT. Must be tall enough that a
87
- // single line of AA text (glyph height ~lineHeight, typically 16-22px at ts=2)
88
- // fits within one band if a glyph straddles a band boundary, its anti-
89
- // aliased edge pixels blend toward the wrong bg in the adjacent band, visible
90
- // as a clipped/faded text bottom. 32 ensures most fonts fit; at 480px wide
91
- // that's ~30KB (fits SRAM on most ESP32 targets).
92
- #ifndef UI_STRIP_BAND_HEIGHT
93
- #define UI_STRIP_BAND_HEIGHT 32
94
- #endif
95
- #if defined(ESP32) || defined(ESP8266)
96
- #include <Esp.h>
97
- #endif
98
- `;
99
- }
1
+ // Slice of the C++ runtime header (original source lines 17-89).
2
+ // Guard open, includes, and #define knobs.
3
+ // See docs/superpowers/specs/2026-07-12-split-runtime-header-design.md.
4
+ export function emitTypesDefines(): string {
5
+ return `
6
+ // ── Cuttlefish UI runtime (emit once per TU) ──────────────────────────────
7
+ #ifndef __TC_UI_RUNTIME
8
+ #define __TC_UI_RUNTIME
9
+ #include <stdint.h>
10
+ #include <cstring>
11
+ #include <new>
12
+ #define UI_TEXT_BUF 32 // max stored UI text chars, excluding the trailing NUL
13
+ #define UI_TEXT_LINE_BUF 96
14
+ #define UI_WS_NORMAL 0
15
+ #define UI_WS_NOWRAP 1
16
+ #define UI_WS_PRE 2
17
+ #define UI_WS_PRE_LINE 3
18
+ #ifndef UI_MAX_BUFFERED_PAINT_PIXELS
19
+ #define UI_MAX_BUFFERED_PAINT_PIXELS 20000
20
+ #endif
21
+ // Above this many pixels, prefer the band renderer (small ~10KB SRAM canvas,
22
+ // strip-at-a-time) over the repair canvas even when the repair canvas could
23
+ // allocate (e.g. in PSRAM). A large repair canvas (a full-width button is
24
+ // ~15-30KB) has higher alloc/seed/push latency than banding — the per-band
25
+ // push is smaller and the band canvas lives in fast internal SRAM. Set below
26
+ // UI_MAX_BUFFERED_PAINT_PIXELS so large rects band while small repaints keep
27
+ // the fast single-canvas composite. Tune per target; 0 = always prefer the
28
+ // repair canvas when it allocates (the pre-PSRAM behavior).
29
+ #ifndef UI_BAND_PREFER_PIXELS
30
+ #define UI_BAND_PREFER_PIXELS 8000
31
+ #endif
32
+ #ifndef UI_USE_FULL_FRAMEBUFFER
33
+ #define UI_USE_FULL_FRAMEBUFFER 0
34
+ #endif
35
+ #ifndef UI_TRANSITION_SNAP_MS
36
+ #define UI_TRANSITION_SNAP_MS 100
37
+ #endif
38
+ // Scroll physics + capability defaults. The per-TU #define overrides emitted
39
+ // by the UI emitter (from resolveScrollConfig) hit BEFORE this header, so these
40
+ // #ifndef guards adopt the configured values. Spec: 2026-06-28-scroll-engine-rewrite-design.md
41
+ #ifndef UI_SCROLL_MAX_OVERSCROLL
42
+ #define UI_SCROLL_MAX_OVERSCROLL 40
43
+ #endif
44
+ #ifndef UI_SCROLL_STIFFNESS_X10
45
+ #define UI_SCROLL_STIFFNESS_X10 5
46
+ #endif
47
+ #ifndef UI_SCROLL_EDGE_SNAP_PX
48
+ #define UI_SCROLL_EDGE_SNAP_PX 12
49
+ #endif
50
+ #ifndef UI_SCROLL_DRAG_SCALE_X10
51
+ #define UI_SCROLL_DRAG_SCALE_X10 10
52
+ #endif
53
+ #ifndef UI_SCROLL_SETTLE_MS
54
+ #define UI_SCROLL_SETTLE_MS 180
55
+ #endif
56
+ #ifndef UI_SCROLL_DEADBAND_PX
57
+ #define UI_SCROLL_DEADBAND_PX 2
58
+ #endif
59
+ // Capability tier flags (emitted per-TU before this header; defaults = full).
60
+ // The default-on check must run BEFORE the per-flag #ifndef defaults below —
61
+ // after them, all three flags are defined 0 and the fallback would redefine
62
+ // UI_SCROLL_INPUT_TIER_RESISTIVE with a different value (ill-formed).
63
+ #if !defined(UI_SCROLL_INPUT_TIER_CAPACITIVE) && !defined(UI_SCROLL_INPUT_TIER_RESISTIVE) && !defined(UI_SCROLL_INPUT_TIER_NONE)
64
+ #define UI_SCROLL_INPUT_TIER_RESISTIVE 1
65
+ #endif
66
+ #ifndef UI_SCROLL_INPUT_TIER_CAPACITIVE
67
+ #define UI_SCROLL_INPUT_TIER_CAPACITIVE 0
68
+ #endif
69
+ #ifndef UI_SCROLL_INPUT_TIER_RESISTIVE
70
+ #define UI_SCROLL_INPUT_TIER_RESISTIVE 0
71
+ #endif
72
+ #ifndef UI_SCROLL_INPUT_TIER_NONE
73
+ #define UI_SCROLL_INPUT_TIER_NONE 0
74
+ #endif
75
+ #ifndef UI_SCROLL_RENDER_TIER_FULL
76
+ #define UI_SCROLL_RENDER_TIER_FULL 1
77
+ #endif
78
+ #ifndef UI_SCROLL_RENDER_TIER_CONSTRAINED
79
+ #define UI_SCROLL_RENDER_TIER_CONSTRAINED 0
80
+ #endif
81
+ #define UI_SCROLL_HAS_TOUCH (UI_SCROLL_INPUT_TIER_CAPACITIVE || UI_SCROLL_INPUT_TIER_RESISTIVE)
82
+ #define UI_SCROLL_ELASTIC (UI_SCROLL_RENDER_TIER_FULL)
83
+ // Telemetry: emits per-frame scrollY/overscrollPx/dy over Serial when defined.
84
+ #ifndef UI_SCROLL_DEBUG
85
+ #define UI_SCROLL_DEBUG 0
86
+ #endif
87
+ // Band renderer: height (px) of the horizontal band canvas used to composite
88
+ // scroll subtrees tear-free when no viewport canvas fits (no PSRAM / over
89
+ // budget). Each band is vw × UI_STRIP_BAND_HEIGHT. Must be tall enough that a
90
+ // single line of AA text (glyph height ~lineHeight, typically 16-22px at ts=2)
91
+ // fits within one band if a glyph straddles a band boundary, its anti-
92
+ // aliased edge pixels blend toward the wrong bg in the adjacent band, visible
93
+ // as a clipped/faded text bottom. 32 ensures most fonts fit; at 480px wide
94
+ // that's ~30KB (fits SRAM on most ESP32 targets).
95
+ // Dirty-rect merging (LVGL refresh-cycle technique): several same-frame
96
+ // dirty nodes whose paint rects are near each other composite as one banded
97
+ // region instead of per-node transactions. Regions cap at the band-
98
+ // compositable pixel budget; singleton rects keep the per-node ladder.
99
+ #ifndef UI_MERGE_INFLATE
100
+ #define UI_MERGE_INFLATE 8
101
+ #endif
102
+ #ifndef UI_MERGE_MAX_REGIONS
103
+ #define UI_MERGE_MAX_REGIONS 6
104
+ #endif
105
+ #ifndef UI_MERGE_MAX_NODES
106
+ #define UI_MERGE_MAX_NODES 12
107
+ #endif
108
+
109
+ #ifndef UI_STRIP_BAND_HEIGHT
110
+ #define UI_STRIP_BAND_HEIGHT 32
111
+ #endif
112
+ #if defined(ESP32) || defined(ESP8266)
113
+ #include <Esp.h>
114
+ #endif
115
+ `;
116
+ }