@flyingrobots/bijou-tui 4.4.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +244 -21
  2. package/dist/animate.d.ts +4 -0
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +47 -20
  5. package/dist/animate.js.map +1 -1
  6. package/dist/app-frame-actions.d.ts.map +1 -1
  7. package/dist/app-frame-actions.js +55 -0
  8. package/dist/app-frame-actions.js.map +1 -1
  9. package/dist/app-frame-i18n.d.ts.map +1 -1
  10. package/dist/app-frame-i18n.js +51 -0
  11. package/dist/app-frame-i18n.js.map +1 -1
  12. package/dist/app-frame-layers.d.ts +11 -1
  13. package/dist/app-frame-layers.d.ts.map +1 -1
  14. package/dist/app-frame-layers.js +19 -0
  15. package/dist/app-frame-layers.js.map +1 -1
  16. package/dist/app-frame-overlays.d.ts +82 -0
  17. package/dist/app-frame-overlays.d.ts.map +1 -0
  18. package/dist/app-frame-overlays.js +480 -0
  19. package/dist/app-frame-overlays.js.map +1 -0
  20. package/dist/app-frame-palette.d.ts.map +1 -1
  21. package/dist/app-frame-palette.js +36 -16
  22. package/dist/app-frame-palette.js.map +1 -1
  23. package/dist/app-frame-render.d.ts.map +1 -1
  24. package/dist/app-frame-render.js +16 -7
  25. package/dist/app-frame-render.js.map +1 -1
  26. package/dist/app-frame-types.d.ts +141 -5
  27. package/dist/app-frame-types.d.ts.map +1 -1
  28. package/dist/app-frame-types.js +17 -1
  29. package/dist/app-frame-types.js.map +1 -1
  30. package/dist/app-frame-utils.d.ts +3 -1
  31. package/dist/app-frame-utils.d.ts.map +1 -1
  32. package/dist/app-frame-utils.js +11 -5
  33. package/dist/app-frame-utils.js.map +1 -1
  34. package/dist/app-frame.d.ts +22 -86
  35. package/dist/app-frame.d.ts.map +1 -1
  36. package/dist/app-frame.js +475 -629
  37. package/dist/app-frame.js.map +1 -1
  38. package/dist/browsable-list.d.ts +12 -0
  39. package/dist/browsable-list.d.ts.map +1 -1
  40. package/dist/browsable-list.js +17 -6
  41. package/dist/browsable-list.js.map +1 -1
  42. package/dist/canvas.d.ts +11 -3
  43. package/dist/canvas.d.ts.map +1 -1
  44. package/dist/canvas.js +146 -13
  45. package/dist/canvas.js.map +1 -1
  46. package/dist/cell-glyph-fit.d.ts +20 -0
  47. package/dist/cell-glyph-fit.d.ts.map +1 -0
  48. package/dist/cell-glyph-fit.js +82 -0
  49. package/dist/cell-glyph-fit.js.map +1 -0
  50. package/dist/collection-surface.d.ts +8 -0
  51. package/dist/collection-surface.d.ts.map +1 -1
  52. package/dist/collection-surface.js +72 -8
  53. package/dist/collection-surface.js.map +1 -1
  54. package/dist/command-palette.d.ts +2 -0
  55. package/dist/command-palette.d.ts.map +1 -1
  56. package/dist/command-palette.js +36 -19
  57. package/dist/command-palette.js.map +1 -1
  58. package/dist/css/text-style.d.ts +2 -0
  59. package/dist/css/text-style.d.ts.map +1 -1
  60. package/dist/css/text-style.js +18 -8
  61. package/dist/css/text-style.js.map +1 -1
  62. package/dist/debug-overlay.d.ts +19 -0
  63. package/dist/debug-overlay.d.ts.map +1 -0
  64. package/dist/debug-overlay.js +25 -0
  65. package/dist/debug-overlay.js.map +1 -0
  66. package/dist/design-language.d.ts.map +1 -1
  67. package/dist/design-language.js +4 -5
  68. package/dist/design-language.js.map +1 -1
  69. package/dist/driver.d.ts +102 -0
  70. package/dist/driver.d.ts.map +1 -1
  71. package/dist/driver.js +259 -19
  72. package/dist/driver.js.map +1 -1
  73. package/dist/eventbus.d.ts +20 -0
  74. package/dist/eventbus.d.ts.map +1 -1
  75. package/dist/eventbus.js +47 -0
  76. package/dist/eventbus.js.map +1 -1
  77. package/dist/file-picker.d.ts.map +1 -1
  78. package/dist/file-picker.js +2 -2
  79. package/dist/file-picker.js.map +1 -1
  80. package/dist/flex.d.ts.map +1 -1
  81. package/dist/flex.js +6 -6
  82. package/dist/flex.js.map +1 -1
  83. package/dist/focus-area.d.ts +13 -4
  84. package/dist/focus-area.d.ts.map +1 -1
  85. package/dist/focus-area.js +56 -14
  86. package/dist/focus-area.js.map +1 -1
  87. package/dist/focus-map.d.ts +40 -0
  88. package/dist/focus-map.d.ts.map +1 -0
  89. package/dist/focus-map.js +133 -0
  90. package/dist/focus-map.js.map +1 -0
  91. package/dist/grid.d.ts +2 -2
  92. package/dist/grid.d.ts.map +1 -1
  93. package/dist/grid.js +11 -148
  94. package/dist/grid.js.map +1 -1
  95. package/dist/help.d.ts +2 -0
  96. package/dist/help.d.ts.map +1 -1
  97. package/dist/help.js +6 -4
  98. package/dist/help.js.map +1 -1
  99. package/dist/icon-presentation.d.ts +10 -0
  100. package/dist/icon-presentation.d.ts.map +1 -0
  101. package/dist/icon-presentation.js +12 -0
  102. package/dist/icon-presentation.js.map +1 -0
  103. package/dist/index.d.ts +32 -9
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +17 -5
  106. package/dist/index.js.map +1 -1
  107. package/dist/input-map.d.ts +68 -0
  108. package/dist/input-map.d.ts.map +1 -0
  109. package/dist/input-map.js +162 -0
  110. package/dist/input-map.js.map +1 -0
  111. package/dist/input-routing-inspector.d.ts +24 -0
  112. package/dist/input-routing-inspector.d.ts.map +1 -0
  113. package/dist/input-routing-inspector.js +76 -0
  114. package/dist/input-routing-inspector.js.map +1 -0
  115. package/dist/keybindings.d.ts.map +1 -1
  116. package/dist/keybindings.js +3 -0
  117. package/dist/keybindings.js.map +1 -1
  118. package/dist/layout-inspector.d.ts +31 -0
  119. package/dist/layout-inspector.d.ts.map +1 -0
  120. package/dist/layout-inspector.js +133 -0
  121. package/dist/layout-inspector.js.map +1 -0
  122. package/dist/layout-preset.d.ts +1 -1
  123. package/dist/layout-preset.d.ts.map +1 -1
  124. package/dist/motion/reconciler.d.ts +6 -2
  125. package/dist/motion/reconciler.d.ts.map +1 -1
  126. package/dist/motion/reconciler.js +40 -10
  127. package/dist/motion/reconciler.js.map +1 -1
  128. package/dist/motion/types.d.ts +3 -1
  129. package/dist/motion/types.d.ts.map +1 -1
  130. package/dist/navigable-table.d.ts +9 -10
  131. package/dist/navigable-table.d.ts.map +1 -1
  132. package/dist/navigable-table.js +33 -14
  133. package/dist/navigable-table.js.map +1 -1
  134. package/dist/notification.d.ts +15 -0
  135. package/dist/notification.d.ts.map +1 -1
  136. package/dist/notification.js +136 -34
  137. package/dist/notification.js.map +1 -1
  138. package/dist/overlay.d.ts.map +1 -1
  139. package/dist/overlay.js +40 -14
  140. package/dist/overlay.js.map +1 -1
  141. package/dist/pager.d.ts +3 -1
  142. package/dist/pager.d.ts.map +1 -1
  143. package/dist/pager.js +4 -0
  144. package/dist/pager.js.map +1 -1
  145. package/dist/pipeline/middleware/grayscale.d.ts.map +1 -1
  146. package/dist/pipeline/middleware/grayscale.js +5 -5
  147. package/dist/pipeline/middleware/grayscale.js.map +1 -1
  148. package/dist/pipeline/middleware/motion.d.ts.map +1 -1
  149. package/dist/pipeline/middleware/motion.js +4 -3
  150. package/dist/pipeline/middleware/motion.js.map +1 -1
  151. package/dist/pipeline/middleware/paint.d.ts.map +1 -1
  152. package/dist/pipeline/middleware/paint.js +2 -1
  153. package/dist/pipeline/middleware/paint.js.map +1 -1
  154. package/dist/pipeline/middleware/surface-shaders.d.ts +37 -0
  155. package/dist/pipeline/middleware/surface-shaders.d.ts.map +1 -0
  156. package/dist/pipeline/middleware/surface-shaders.js +164 -0
  157. package/dist/pipeline/middleware/surface-shaders.js.map +1 -0
  158. package/dist/pipeline/pipeline.d.ts +37 -2
  159. package/dist/pipeline/pipeline.d.ts.map +1 -1
  160. package/dist/pipeline/pipeline.js +121 -22
  161. package/dist/pipeline/pipeline.js.map +1 -1
  162. package/dist/raytrace.d.ts +59 -0
  163. package/dist/raytrace.d.ts.map +1 -0
  164. package/dist/raytrace.js +121 -0
  165. package/dist/raytrace.js.map +1 -0
  166. package/dist/runtime-binding.d.ts +59 -0
  167. package/dist/runtime-binding.d.ts.map +1 -0
  168. package/dist/runtime-binding.js +271 -0
  169. package/dist/runtime-binding.js.map +1 -0
  170. package/dist/runtime.d.ts +19 -0
  171. package/dist/runtime.d.ts.map +1 -1
  172. package/dist/runtime.js +222 -31
  173. package/dist/runtime.js.map +1 -1
  174. package/dist/split-pane.d.ts +2 -2
  175. package/dist/split-pane.d.ts.map +1 -1
  176. package/dist/split-pane.js +28 -49
  177. package/dist/split-pane.js.map +1 -1
  178. package/dist/subapp/mount.d.ts +17 -0
  179. package/dist/subapp/mount.d.ts.map +1 -1
  180. package/dist/subapp/mount.js +13 -0
  181. package/dist/subapp/mount.js.map +1 -1
  182. package/dist/surface-budget.d.ts +27 -0
  183. package/dist/surface-budget.d.ts.map +1 -0
  184. package/dist/surface-budget.js +61 -0
  185. package/dist/surface-budget.js.map +1 -0
  186. package/dist/surface-diff.d.ts +36 -0
  187. package/dist/surface-diff.d.ts.map +1 -0
  188. package/dist/surface-diff.js +202 -0
  189. package/dist/surface-diff.js.map +1 -0
  190. package/dist/surface-layout.d.ts +19 -3
  191. package/dist/surface-layout.d.ts.map +1 -1
  192. package/dist/surface-layout.js +42 -12
  193. package/dist/surface-layout.js.map +1 -1
  194. package/dist/types.d.ts +8 -0
  195. package/dist/types.d.ts.map +1 -1
  196. package/dist/types.js.map +1 -1
  197. package/dist/view-output.js +2 -2
  198. package/dist/view-output.js.map +1 -1
  199. package/dist/viewport.d.ts +13 -1
  200. package/dist/viewport.d.ts.map +1 -1
  201. package/dist/viewport.js +33 -11
  202. package/dist/viewport.js.map +1 -1
  203. package/package.json +4 -4
package/README.md CHANGED
@@ -7,7 +7,7 @@ The high-fidelity TEA runtime for Bijou.
7
7
  ## Role
8
8
 
9
9
  - **The Elm Architecture (TEA)**: A deterministic state-update-view loop for industrial-strength terminal software.
10
- - **Fractal TEA**: Compose nested sub-apps with `initSubApp()`, `updateSubApp()`, and `mount()`.
10
+ - **Fractal TEA**: Compose nested sub-apps with `createSubAppAdapter()`, `initSubApp()`, `updateSubApp()`, and `mount()`.
11
11
  - **Declarative Motion**: Interpolate layout changes smoothly with physics-based springs and tween animations.
12
12
  - **Surface-First Pipeline**: Programmable rendering middleware for fragments, diffing, and shader-based transitions.
13
13
 
@@ -20,18 +20,18 @@ npm install @flyingrobots/bijou @flyingrobots/bijou-node @flyingrobots/bijou-tui
20
20
  ## Quick Start (Sub-App Composition)
21
21
 
22
22
  ```typescript
23
- import { initDefaultContext } from '@flyingrobots/bijou-node';
24
- import { run, mount, type App } from '@flyingrobots/bijou-tui';
23
+ import { startApp } from '@flyingrobots/bijou-node';
24
+ import { createSubAppAdapter, mount, type App } from '@flyingrobots/bijou-tui';
25
25
  import { createSurface } from '@flyingrobots/bijou';
26
26
 
27
- initDefaultContext();
28
-
29
- type ChildModel = { count: number };
30
- type ChildMsg = { type: 'noop' };
31
- type Model = { left: ChildModel; right: ChildModel };
32
- type Msg = { pane: 'left' | 'right'; msg: ChildMsg };
27
+ type ChildMsg = { type: 'tick' };
28
+ type ParentModel = {
29
+ left: { count: number };
30
+ right: { count: number };
31
+ };
32
+ type ParentMsg = { type: 'left'; msg: ChildMsg } | { type: 'right'; msg: ChildMsg };
33
33
 
34
- const childApp: App<ChildModel, ChildMsg> = {
34
+ const childApp: App<{ count: number }, ChildMsg> = {
35
35
  init: () => [{ count: 0 }, []],
36
36
  update: (msg, model) => [model, []],
37
37
  view: (model) => {
@@ -41,18 +41,20 @@ const childApp: App<ChildModel, ChildMsg> = {
41
41
  }
42
42
  };
43
43
 
44
- const app: App<Model, Msg> = {
44
+ const mapLeft = createSubAppAdapter<ParentMsg, ChildMsg>({
45
+ tick: (msg) => ({ type: 'left', msg }),
46
+ });
47
+
48
+ const mapRight = createSubAppAdapter<ParentMsg, ChildMsg>({
49
+ tick: (msg) => ({ type: 'right', msg }),
50
+ });
51
+
52
+ const app: App<ParentModel, ParentMsg> = {
45
53
  init: () => [{ left: { count: 0 }, right: { count: 0 } }, []],
46
54
  update: (msg, model) => [model, []],
47
55
  view: (model) => {
48
- const [left] = mount(childApp, {
49
- model: model.left,
50
- onMsg: (msg) => ({ pane: 'left', msg }),
51
- });
52
- const [right] = mount(childApp, {
53
- model: model.right,
54
- onMsg: (msg) => ({ pane: 'right', msg }),
55
- });
56
+ const [left] = mount(childApp, { model: model.left, onMsg: mapLeft });
57
+ const [right] = mount(childApp, { model: model.right, onMsg: mapRight });
56
58
 
57
59
  const screen = createSurface(80, 24);
58
60
  screen.blit(left, 0, 0);
@@ -61,9 +63,21 @@ const app: App<Model, Msg> = {
61
63
  }
62
64
  };
63
65
 
64
- run(app);
66
+ await startApp(app);
65
67
  ```
66
68
 
69
+ For Node hosts, prefer `startApp()` for the first-app path. Reach for
70
+ `run(app, { ctx })` when the host owns context creation explicitly.
71
+
72
+ When you need to mix small string fragments with surface-returning primitives,
73
+ keep composition on the surface side: use `contentSurface()` directly or pass
74
+ strings into `vstackSurface()` / `hstackSurface()`. Raw strings are still not a
75
+ valid `view()` return type.
76
+
77
+ Use `proseSurface(text, { width })` when paragraph-like text should wrap at
78
+ word boundaries before becoming a surface. Keep `contentSurface()` for
79
+ preformatted output, tables, boxes, and caller-managed line breaks.
80
+
67
81
  ## Strategy: Choosing Component Families
68
82
 
69
83
  Select the family based on the interaction semantic.
@@ -73,6 +87,15 @@ Select the family based on the interaction semantic.
73
87
  - **`modal()`**: Required decision that blocks background activity.
74
88
  - **`toast()`**: Transient notification for a single event.
75
89
  - **`tooltip()`**: Micro-explanation for a local target.
90
+ - **`debugOverlay()`**: Development-only perf HUD composited onto any app surface.
91
+ - **`layoutInspectorOverlay()`**: Development-only geometry HUD for region
92
+ bounds, focus, and layers.
93
+ - **`inputRoutingInspectorText()`**: Development-only route trace for recent
94
+ key and pointer events.
95
+ - **`focusMapText()`**: Development-only focus ownership and tab-order report.
96
+ - **`evaluateSurfaceBudget()`**: Deterministic warnings for surface and timing
97
+ thresholds.
98
+ - **`surfaceDiffSurface()`**: Side-by-side or overlay inspection for two rendered surfaces.
76
99
 
77
100
  ### Collection Interaction
78
101
  - **`navigableTable()`**: Keyboard-driven traversal and cell inspection.
@@ -80,11 +103,43 @@ Select the family based on the interaction semantic.
80
103
  - **`commandPalette()`**: Action discovery and navigation.
81
104
 
82
105
  ### Shell and Workspace Layout
83
- - **`createFramedApp()`**: Batteries-included workspace with tabs, panes, and help.
106
+ - **`createFramedApp()`**: Batteries-included workspace with tabs, panes,
107
+ help, and a shell-owned perf HUD toggled with the backtick key.
84
108
  - **`splitPane()`**: Dynamic primary/secondary context comparison.
85
109
  - **`grid()`**: Stable regions with simultaneous visibility.
86
110
  - **`viewport()`**: The canonical scroll mask for rich composition.
87
111
 
112
+ For framed shells, Node hosts can still prefer `startApp(app)`: the hosted
113
+ Node bootstrap delegates to self-running framed apps automatically. Use the
114
+ explicit runner path when you want to stay inside `@flyingrobots/bijou-tui`
115
+ or when the host owns `ctx` directly:
116
+
117
+ ```typescript
118
+ import { createFramedApp, runFramedApp } from '@flyingrobots/bijou-tui';
119
+
120
+ const app = createFramedApp({ pages: [page] });
121
+
122
+ await app.run({ ctx });
123
+ // or: await runFramedApp({ pages: [page] }, { ctx });
124
+ ```
125
+
126
+ This path keeps the shell batteries included: mouse input defaults to `true`,
127
+ the shared runtime loop still does the heavy lifting, and frame timing/budget
128
+ telemetry stays attached to the frame model for shell-owned UI. For Node-hosted
129
+ apps, `startApp(app)` remains the default bootstrap.
130
+
131
+ ## Runtime Diagnostics
132
+
133
+ The EventBus exposes command queue facts through `getCommandDiagnostics()`.
134
+ `run(app, { commandBackpressureThreshold })` routes a warning when pending
135
+ commands cross the threshold, without hard-capping legitimate bursts. The
136
+ default threshold is 1000; use `0` to disable the warning.
137
+
138
+ Render pipeline middleware is synchronous by contract. If middleware returns a
139
+ Promise, Bijou reports a pipeline diagnostic, keeps the current frame moving,
140
+ and ignores any late `next()` continuation so stage ordering stays
141
+ deterministic.
142
+
88
143
  ## Animation
89
144
 
90
145
  ### Spring Physics
@@ -99,6 +154,11 @@ const cmd = animate({
99
154
  });
100
155
  ```
101
156
 
157
+ Spring commands integrate physics with bounded fixed timesteps by default, so a
158
+ slow terminal frame cannot feed a large raw pulse delta into the spring. Tune
159
+ `fixedStepSeconds` and `maxPulseSeconds` only when an app needs a custom
160
+ simulation cadence.
161
+
102
162
  ### Timeline Orchestration
103
163
  ```typescript
104
164
  import { timeline } from '@flyingrobots/bijou-tui';
@@ -110,10 +170,173 @@ const tl = timeline()
110
170
  .build();
111
171
  ```
112
172
 
173
+ ## Post-Process Shaders
174
+
175
+ `canvas()` can render procedural surfaces at cell, Quad, Braille, or glyph-fit
176
+ resolution. Use `resolution: 'glyph'` when a shader should sample a 2x4 grid but
177
+ collapse into geometry-aware Unicode or ASCII characters instead of Braille.
178
+
179
+ ```typescript
180
+ import { canvas } from '@flyingrobots/bijou-tui';
181
+
182
+ const logo = canvas(24, 6, ({ u, v }) => {
183
+ const lit = Math.abs(u - v) < 0.08;
184
+ return lit ? { char: 'X', fg: '#ffffff' } : ' ';
185
+ }, { resolution: 'glyph' });
186
+ ```
187
+
188
+ ```typescript
189
+ import { run, surfaceShaderFilter, scanlines, vignette } from '@flyingrobots/bijou-tui';
190
+
191
+ await run(app, {
192
+ configurePipeline(pipeline) {
193
+ pipeline.use('PostProcess', surfaceShaderFilter(
194
+ scanlines({ dimFactor: 0.82 }),
195
+ vignette({ edgeFactor: 0.78 }),
196
+ ));
197
+ },
198
+ });
199
+ ```
200
+
201
+ Use `surfaceShaderFilter(...)` to compose built-in post-process passes like
202
+ `scanlines()`, `flicker()`, `noise()`, and `vignette()` over the packed target
203
+ surface before diff/output.
204
+
205
+ ## Testing
206
+
207
+ Use `testRuntime()` when you want an inspectable harness instead of a
208
+ one-shot script result:
209
+
210
+ ```typescript
211
+ import { testRuntime } from '@flyingrobots/bijou-tui';
212
+
213
+ const harness = await testRuntime(app, { ctx });
214
+ await harness.press('q');
215
+
216
+ expect(harness.frame).toBeDefined();
217
+ expect(harness.messages).toHaveLength(1);
218
+ expect(harness.commands.every((record) => record.settled)).toBe(true);
219
+
220
+ await harness.teardown();
221
+ ```
222
+
223
+ Keep `runScript()` for fixture-style interaction playback and GIF/demo
224
+ capture, and use `testRuntime()` when you need direct assertions on
225
+ snapshots, emitted messages, command outcomes, or cleanup disposal.
226
+
227
+ Use `surfaceDiffText()` or `surfaceDiffSurface()` when a frame assertion fails
228
+ and you need cell-level truth instead of a raw string dump:
229
+
230
+ ```typescript
231
+ import { surfaceDiffText } from '@flyingrobots/bijou-tui';
232
+
233
+ expect(surfaceDiffText(before, after)).toContain('surface diff: 2 changed');
234
+ ```
235
+
236
+ Use `layoutInspectorOverlay()` when a layout assertion needs visual geometry
237
+ truth on top of the rendered frame:
238
+
239
+ ```typescript
240
+ import {
241
+ layoutInspectorOverlay,
242
+ layoutInspectorText,
243
+ } from '@flyingrobots/bijou-tui';
244
+
245
+ const inspected = layoutInspectorOverlay(frame, [
246
+ {
247
+ id: 'editor',
248
+ rect: { col: 2, row: 1, width: 40, height: 12 },
249
+ focused: true,
250
+ },
251
+ ]);
252
+
253
+ expect(layoutInspectorText([
254
+ { id: 'editor', rect: { x: 2, y: 1, width: 40, height: 12 } },
255
+ ]))
256
+ .toContain('focused=false');
257
+ ```
258
+
259
+ Use `evaluateSurfaceBudget()` directly in tests or pass `surfaceBudget` to
260
+ `run()` when an interactive app should route non-fatal runtime warnings:
261
+
262
+ ```typescript
263
+ import { evaluateSurfaceBudget, run } from '@flyingrobots/bijou-tui';
264
+
265
+ expect(evaluateSurfaceBudget({
266
+ surface: frame,
267
+ thresholds: { maxArea: 4000 },
268
+ })).toEqual([]);
269
+
270
+ await run(app, {
271
+ ctx,
272
+ surfaceBudget: { maxArea: 4000, maxStageDurationMs: { Paint: 8 } },
273
+ });
274
+ ```
275
+
276
+ Use `appendInputRoutingRecord()` and `inputRoutingInspectorText()` when a shell
277
+ or app wants to keep the last few routing decisions visible:
278
+
279
+ ```typescript
280
+ import {
281
+ appendInputRoutingRecord,
282
+ inputRoutingInspectorText,
283
+ } from '@flyingrobots/bijou-tui';
284
+
285
+ const history = appendInputRoutingRecord({ records: [] }, {
286
+ event,
287
+ result,
288
+ commandLabels: ['save-file'],
289
+ });
290
+
291
+ console.log(inputRoutingInspectorText(history));
292
+ ```
293
+
294
+ Use `createInputGestureRecognizer()` and `createInputActionMap()` when raw input
295
+ needs to become semantic gestures before routing. Bijou ships standard feature
296
+ event names such as `press`, `held`, `release`, `tap`, `double-tap`,
297
+ `long-press`, and lets apps provide their own event type strings.
298
+
299
+ ```typescript
300
+ import {
301
+ createInputActionMap,
302
+ createInputGestureRecognizer,
303
+ } from '@flyingrobots/bijou-tui';
304
+
305
+ const recognizer = createInputGestureRecognizer({ doubleTapMs: 300 });
306
+ const actions = createInputActionMap<{ type: 'toggle-footer' }>()
307
+ .bind('footer.doubleTab', 'Toggle footer', [
308
+ { deviceId: 'keyboard', featureId: 'key.tab', type: 'double-tap' },
309
+ ], { type: 'toggle-footer' });
310
+
311
+ const action = actions.handle(recognizer.observeKey(keyMsg, ctx.runtime.clock.now()));
312
+ ```
313
+
314
+ Use `focusMapText()` or `focusMapSurface()` when focus ownership and tab order
315
+ need the same deterministic treatment:
316
+
317
+ ```typescript
318
+ import { focusMapText } from '@flyingrobots/bijou-tui';
319
+
320
+ const report = focusMapText([
321
+ {
322
+ id: 'editor',
323
+ owner: 'workspace',
324
+ role: 'textbox',
325
+ rect: { x: 2, y: 1, width: 60, height: 20 },
326
+ tabIndex: 1,
327
+ focusable: true,
328
+ focused: true,
329
+ },
330
+ ]);
331
+
332
+ expect(report).toContain('[1] *editor');
333
+ ```
334
+
113
335
  ## Documentation
114
336
 
115
337
  - **[GUIDE.md](./GUIDE.md)**: Productive-fast path for building apps.
116
338
  - **[ADVANCED_GUIDE.md](./ADVANCED_GUIDE.md)**: Shell doctrine, shaders, and motion internals.
339
+ - **[Render Pipeline Guide](../../docs/guides/render-pipeline.md)**: Stage order, `RenderState`, and `configurePipeline()` truth.
117
340
  - **[Design System](../../docs/design-system/README.md)**: Semantic guidance and patterns.
118
341
 
119
342
  ---
package/dist/animate.d.ts CHANGED
@@ -43,6 +43,10 @@ export interface SpringAnimateOptions<M> extends AnimateBase<M> {
43
43
  readonly type?: 'spring';
44
44
  /** Spring config — preset name or custom values. */
45
45
  readonly spring?: Partial<SpringConfig> | SpringPreset;
46
+ /** Integration step in seconds. Defaults to 1/120 for stable spring physics. */
47
+ readonly fixedStepSeconds?: number;
48
+ /** Maximum pulse time accepted in one runtime pulse. Defaults to 1/20. */
49
+ readonly maxPulseSeconds?: number;
46
50
  }
47
51
  /**
48
52
  * Options for a duration-based tween animation.
@@ -1 +1 @@
1
- {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,EAQd,MAAM,aAAa,CAAC;AAMrB;;;;GAIG;AACH,UAAU,WAAW,CAAC,CAAC;IACrB,sBAAsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAC;IAC7D,kFAAkF;IAClF,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;CACxD;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAC;IAC5D,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAMjF;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAiB7D;AA+FD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAMrD"}
1
+ {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,EAQd,MAAM,aAAa,CAAC;AASrB;;;;GAIG;AACH,UAAU,WAAW,CAAC,CAAC;IACrB,sBAAsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAC;IAC7D,kFAAkF;IAClF,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IACvD,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAC;IAC5D,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAMjF;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CA2B7D;AAyID;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAMrD"}
package/dist/animate.js CHANGED
@@ -15,6 +15,8 @@
15
15
  * the target value in a single frame.
16
16
  */
17
17
  import { springStep, createSpringState, resolveSpringConfig, tweenStep, createTweenState, resolveTweenConfig, EASINGS, } from './spring.js';
18
+ const DEFAULT_SPRING_FIXED_STEP_SECONDS = 1 / 120;
19
+ const DEFAULT_SPRING_MAX_PULSE_SECONDS = 1 / 20;
18
20
  // ---------------------------------------------------------------------------
19
21
  // animate() — the main API
20
22
  // ---------------------------------------------------------------------------
@@ -44,28 +46,43 @@ export function animate(options) {
44
46
  return createTweenCmd(from, to, options.duration, options.ease ?? EASINGS.easeOutCubic, onFrame, onComplete);
45
47
  }
46
48
  const config = resolveSpringConfig(options.spring);
47
- return createSpringCmd(from, to, config, onFrame, onComplete);
49
+ return createSpringCmd(from, to, config, {
50
+ fixedStepSeconds: resolvePositiveSeconds(options.fixedStepSeconds, DEFAULT_SPRING_FIXED_STEP_SECONDS),
51
+ maxPulseSeconds: resolveMaxPulseSeconds(options.maxPulseSeconds, options.fixedStepSeconds),
52
+ }, onFrame, onComplete);
48
53
  }
49
- // ---------------------------------------------------------------------------
50
- // Internal: spring command
51
- // ---------------------------------------------------------------------------
52
54
  /**
53
- * Build a TEA command that runs a spring animation via `setInterval`.
55
+ * Build a TEA command that advances a spring animation from runtime pulses.
56
+ *
57
+ * The command subscribes through `caps.onPulse`, caps each accepted pulse with
58
+ * `maxPulseSeconds`, and accumulates that time into fixed-step spring physics
59
+ * updates. Slow runtime pulses may delay progress, but they do not feed one
60
+ * large raw delta into the spring integrator.
54
61
  *
55
62
  * @template M - The message type emitted into the TEA update cycle.
56
- * @param from - Starting value.
57
- * @param to - Target value.
58
- * @param config - Resolved spring physics parameters.
59
- * @param fps - Frames per second for the interval timer.
60
- * @param onFrame - Callback invoked each frame with the interpolated value.
61
- * @param onComplete - Optional callback invoked when the spring settles.
63
+ * @param from - Starting value.
64
+ * @param to - Target value.
65
+ * @param config - Resolved spring physics parameters.
66
+ * @param integration - `fixedStepSeconds` and `maxPulseSeconds` integration policy.
67
+ * @param onFrame - Callback invoked after spring physics advances.
68
+ * @param onComplete - Optional callback invoked when the spring settles.
62
69
  * @returns A TEA command that resolves when the spring is done.
63
70
  */
64
- function createSpringCmd(from, to, config, onFrame, onComplete) {
71
+ function createSpringCmd(from, to, config, integration, onFrame, onComplete) {
65
72
  return (emit, caps) => new Promise((resolve) => {
66
73
  let state = createSpringState(from);
74
+ let accumulatedSeconds = 0;
67
75
  const handle = caps.onPulse((dt) => {
68
- state = springStep(state, to, config, dt);
76
+ accumulatedSeconds = Math.min(integration.maxPulseSeconds, accumulatedSeconds + acceptedPulseSeconds(dt, integration.maxPulseSeconds));
77
+ let stepped = false;
78
+ while (accumulatedSeconds >= integration.fixedStepSeconds && !state.done) {
79
+ state = springStep(state, to, config, integration.fixedStepSeconds);
80
+ accumulatedSeconds -= integration.fixedStepSeconds;
81
+ stepped = true;
82
+ }
83
+ if (!stepped) {
84
+ return;
85
+ }
69
86
  emit(onFrame(state.value));
70
87
  if (state.done) {
71
88
  handle.dispose();
@@ -76,23 +93,33 @@ function createSpringCmd(from, to, config, onFrame, onComplete) {
76
93
  });
77
94
  });
78
95
  }
96
+ function resolveMaxPulseSeconds(maxPulseSeconds, fixedStepSeconds) {
97
+ return Math.max(resolvePositiveSeconds(maxPulseSeconds, DEFAULT_SPRING_MAX_PULSE_SECONDS), resolvePositiveSeconds(fixedStepSeconds, DEFAULT_SPRING_FIXED_STEP_SECONDS));
98
+ }
99
+ function resolvePositiveSeconds(value, fallback) {
100
+ return value != null && Number.isFinite(value) && value > 0 ? value : fallback;
101
+ }
102
+ function acceptedPulseSeconds(dt, maxPulseSeconds) {
103
+ if (!Number.isFinite(dt) || dt <= 0) {
104
+ return 0;
105
+ }
106
+ return Math.min(dt, maxPulseSeconds);
107
+ }
79
108
  // ---------------------------------------------------------------------------
80
109
  // Internal: tween command
81
110
  // ---------------------------------------------------------------------------
82
111
  /**
83
- * Build a TEA command that runs a tween animation via `setInterval`.
112
+ * Build a TEA command that advances a tween animation from runtime pulse `dt`.
84
113
  *
85
- * Uses wall-clock time (`Date.now()`) to compute actual elapsed time
86
- * per frame, so animations run at the correct speed regardless of
87
- * timer jitter or system load.
114
+ * The command subscribes through `caps.onPulse`; each pulse supplies elapsed
115
+ * seconds, which are converted to milliseconds before calling `tweenStep()`.
88
116
  *
89
117
  * @template M - The message type emitted into the TEA update cycle.
90
118
  * @param from - Starting value.
91
119
  * @param to - Target value.
92
120
  * @param duration - Total animation duration in milliseconds.
93
121
  * @param ease - Easing function applied to normalized progress.
94
- * @param fps - Frames per second for the interval timer.
95
- * @param onFrame - Callback invoked each frame with the interpolated value.
122
+ * @param onFrame - Callback invoked after tween progress advances.
96
123
  * @param onComplete - Optional callback invoked when the tween completes.
97
124
  * @returns A TEA command that resolves when the tween is done.
98
125
  */
@@ -101,7 +128,7 @@ function createTweenCmd(from, to, duration, ease, onFrame, onComplete) {
101
128
  return (emit, caps) => new Promise((resolve) => {
102
129
  let state = createTweenState(from);
103
130
  const handle = caps.onPulse((dt) => {
104
- // TweenStep expects milliseconds for delta
131
+ // tweenStep expects the pulse delta in milliseconds.
105
132
  state = tweenStep(state, config, dt * 1000);
106
133
  emit(onFrame(state.value));
107
134
  if (state.done) {
@@ -1 +1 @@
1
- {"version":3,"file":"animate.js","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"animate.js","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-frame-actions.d.ts","sourceRoot":"","sources":["../src/app-frame-actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-frame-actions.d.ts","sourceRoot":"","sources":["../src/app-frame-actions.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,9 @@ import { timeline } from './timeline.js';
13
13
  import { EASINGS } from './spring.js';
14
14
  import { collectPaneIds, assertUniquePaneIds, findPaneNode, frameBodyRect, } from './app-frame-utils.js';
15
15
  import { framePaneOutputToSurface, renderFrameNode } from './app-frame-render.js';
16
+ import { animate } from './animate.js';
16
17
  let focusedPaneMeasureScratch = null;
18
+ const FOOTER_TOGGLE_DURATION_MS = 200;
17
19
  function renderPaneSurfaceForMeasurement(output, width, height) {
18
20
  const scratch = focusedPaneMeasureScratch;
19
21
  focusedPaneMeasureScratch = framePaneOutputToSurface(output, width, height, scratch != null && scratch.width === width && scratch.height === height
@@ -26,6 +28,10 @@ export function applyFrameAction(action, model, options, pagesById) {
26
28
  switch (action.type) {
27
29
  case 'toggle-help':
28
30
  return [{ ...model, helpOpen: !model.helpOpen }, []];
31
+ case 'toggle-perf-hud':
32
+ return [{ ...model, perfHudOpen: !model.perfHudOpen }, []];
33
+ case 'toggle-footer':
34
+ return toggleFooter(model);
29
35
  case 'toggle-settings': {
30
36
  const activePage = pagesById.get(model.activePageId);
31
37
  const settings = options.settings?.({
@@ -71,6 +77,8 @@ export function applyFrameAction(action, model, options, pagesById) {
71
77
  quitConfirmOpen: false,
72
78
  }, []];
73
79
  }
80
+ case 'push-notification':
81
+ return [model, []];
74
82
  case 'prev-tab':
75
83
  return switchTab(model, -1, pagesById, options);
76
84
  case 'next-tab':
@@ -109,11 +117,58 @@ export function applyFrameAction(action, model, options, pagesById) {
109
117
  case 'runtime-issue':
110
118
  case 'notification-tick':
111
119
  return [model, []];
120
+ case 'footer-transition':
121
+ if (action.generation !== (model.footerAnimationGeneration ?? 0))
122
+ return [model, []];
123
+ return [{
124
+ ...model,
125
+ footerTranslateY: Math.max(0, Math.min(1, action.translateY)),
126
+ }, []];
127
+ case 'footer-transition-complete':
128
+ if (action.generation !== (model.footerAnimationGeneration ?? 0))
129
+ return [model, []];
130
+ return [{
131
+ ...model,
132
+ footerVisible: action.visible,
133
+ footerTranslateY: action.visible ? 0 : 1,
134
+ }, []];
112
135
  case 'transition':
113
136
  case 'transition-complete':
114
137
  return [model, []];
115
138
  }
116
139
  }
140
+ function toggleFooter(model) {
141
+ const currentVisible = model.footerVisible ?? true;
142
+ const visible = !currentVisible;
143
+ const generation = (model.footerAnimationGeneration ?? 0) + 1;
144
+ const defaultTranslateY = currentVisible ? 0 : 1;
145
+ const from = Math.max(0, Math.min(1, model.footerTranslateY ?? defaultTranslateY));
146
+ const to = visible ? 0 : 1;
147
+ const ease = visible ? EASINGS.easeIn : EASINGS.easeOut;
148
+ return [{
149
+ ...model,
150
+ footerVisible: visible,
151
+ footerAnimationGeneration: generation,
152
+ }, [
153
+ animate({
154
+ type: 'tween',
155
+ from,
156
+ to,
157
+ duration: FOOTER_TOGGLE_DURATION_MS,
158
+ ease,
159
+ onFrame: (translateY) => wrapFrameMsg({
160
+ type: 'footer-transition',
161
+ translateY,
162
+ generation,
163
+ }),
164
+ onComplete: () => wrapFrameMsg({
165
+ type: 'footer-transition-complete',
166
+ visible,
167
+ generation,
168
+ }),
169
+ }),
170
+ ]];
171
+ }
117
172
  function hasNotificationCenter(model, options, pagesById) {
118
173
  const activePage = pagesById.get(model.activePageId);
119
174
  if (activePage == null)