@flyingrobots/bijou-tui 3.1.0 → 4.1.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 (158) hide show
  1. package/LICENSE +159 -21
  2. package/README.md +205 -39
  3. package/dist/app-frame-actions.d.ts +5 -5
  4. package/dist/app-frame-actions.d.ts.map +1 -1
  5. package/dist/app-frame-actions.js +74 -9
  6. package/dist/app-frame-actions.js.map +1 -1
  7. package/dist/app-frame-i18n.d.ts +12 -0
  8. package/dist/app-frame-i18n.d.ts.map +1 -0
  9. package/dist/app-frame-i18n.js +92 -0
  10. package/dist/app-frame-i18n.js.map +1 -0
  11. package/dist/app-frame-layers.d.ts +29 -0
  12. package/dist/app-frame-layers.d.ts.map +1 -0
  13. package/dist/app-frame-layers.js +104 -0
  14. package/dist/app-frame-layers.js.map +1 -0
  15. package/dist/app-frame-palette.d.ts +6 -2
  16. package/dist/app-frame-palette.d.ts.map +1 -1
  17. package/dist/app-frame-palette.js +42 -10
  18. package/dist/app-frame-palette.js.map +1 -1
  19. package/dist/app-frame-render.d.ts +28 -14
  20. package/dist/app-frame-render.d.ts.map +1 -1
  21. package/dist/app-frame-render.js +285 -138
  22. package/dist/app-frame-render.js.map +1 -1
  23. package/dist/app-frame-types.d.ts +41 -21
  24. package/dist/app-frame-types.d.ts.map +1 -1
  25. package/dist/app-frame-types.js +8 -6
  26. package/dist/app-frame-types.js.map +1 -1
  27. package/dist/app-frame-utils.d.ts +8 -3
  28. package/dist/app-frame-utils.d.ts.map +1 -1
  29. package/dist/app-frame-utils.js +42 -27
  30. package/dist/app-frame-utils.js.map +1 -1
  31. package/dist/app-frame.d.ts +128 -12
  32. package/dist/app-frame.d.ts.map +1 -1
  33. package/dist/app-frame.js +1212 -91
  34. package/dist/app-frame.js.map +1 -1
  35. package/dist/browsable-list.d.ts +20 -1
  36. package/dist/browsable-list.d.ts.map +1 -1
  37. package/dist/browsable-list.js +48 -10
  38. package/dist/browsable-list.js.map +1 -1
  39. package/dist/collection-surface.d.ts +8 -0
  40. package/dist/collection-surface.d.ts.map +1 -0
  41. package/dist/collection-surface.js +41 -0
  42. package/dist/collection-surface.js.map +1 -0
  43. package/dist/command-palette.d.ts +17 -1
  44. package/dist/command-palette.d.ts.map +1 -1
  45. package/dist/command-palette.js +50 -20
  46. package/dist/command-palette.js.map +1 -1
  47. package/dist/commands.js +1 -1
  48. package/dist/commands.js.map +1 -1
  49. package/dist/css/text-style.d.ts +2 -1
  50. package/dist/css/text-style.d.ts.map +1 -1
  51. package/dist/css/text-style.js +33 -0
  52. package/dist/css/text-style.js.map +1 -1
  53. package/dist/design-language.d.ts +49 -0
  54. package/dist/design-language.d.ts.map +1 -0
  55. package/dist/design-language.js +70 -0
  56. package/dist/design-language.js.map +1 -0
  57. package/dist/driver.d.ts +6 -1
  58. package/dist/driver.d.ts.map +1 -1
  59. package/dist/driver.js +3 -0
  60. package/dist/driver.js.map +1 -1
  61. package/dist/eventbus.d.ts.map +1 -1
  62. package/dist/eventbus.js +21 -1
  63. package/dist/eventbus.js.map +1 -1
  64. package/dist/file-picker.d.ts +19 -1
  65. package/dist/file-picker.d.ts.map +1 -1
  66. package/dist/file-picker.js +47 -20
  67. package/dist/file-picker.js.map +1 -1
  68. package/dist/flex.d.ts +35 -1
  69. package/dist/flex.d.ts.map +1 -1
  70. package/dist/flex.js +127 -1
  71. package/dist/flex.js.map +1 -1
  72. package/dist/focus-area.d.ts +20 -1
  73. package/dist/focus-area.d.ts.map +1 -1
  74. package/dist/focus-area.js +107 -12
  75. package/dist/focus-area.js.map +1 -1
  76. package/dist/grid.d.ts +10 -0
  77. package/dist/grid.d.ts.map +1 -1
  78. package/dist/grid.js +25 -0
  79. package/dist/grid.js.map +1 -1
  80. package/dist/help.d.ts +41 -0
  81. package/dist/help.d.ts.map +1 -1
  82. package/dist/help.js +50 -0
  83. package/dist/help.js.map +1 -1
  84. package/dist/index.d.ts +23 -17
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +22 -16
  87. package/dist/index.js.map +1 -1
  88. package/dist/inspector-drawer.d.ts +36 -0
  89. package/dist/inspector-drawer.d.ts.map +1 -0
  90. package/dist/inspector-drawer.js +68 -0
  91. package/dist/inspector-drawer.js.map +1 -0
  92. package/dist/layout-node-surface.d.ts +17 -0
  93. package/dist/layout-node-surface.d.ts.map +1 -0
  94. package/dist/layout-node-surface.js +61 -0
  95. package/dist/layout-node-surface.js.map +1 -0
  96. package/dist/navigable-table.d.ts +16 -1
  97. package/dist/navigable-table.d.ts.map +1 -1
  98. package/dist/navigable-table.js +32 -1
  99. package/dist/navigable-table.js.map +1 -1
  100. package/dist/notification.d.ts +26 -1
  101. package/dist/notification.d.ts.map +1 -1
  102. package/dist/notification.js +294 -41
  103. package/dist/notification.js.map +1 -1
  104. package/dist/overlay.d.ts +29 -8
  105. package/dist/overlay.d.ts.map +1 -1
  106. package/dist/overlay.js +248 -137
  107. package/dist/overlay.js.map +1 -1
  108. package/dist/pager.d.ts +16 -0
  109. package/dist/pager.d.ts.map +1 -1
  110. package/dist/pager.js +61 -1
  111. package/dist/pager.js.map +1 -1
  112. package/dist/runtime-engine.d.ts +223 -0
  113. package/dist/runtime-engine.d.ts.map +1 -0
  114. package/dist/runtime-engine.js +457 -0
  115. package/dist/runtime-engine.js.map +1 -0
  116. package/dist/runtime.d.ts.map +1 -1
  117. package/dist/runtime.js +33 -19
  118. package/dist/runtime.js.map +1 -1
  119. package/dist/shell-quit.d.ts +12 -0
  120. package/dist/shell-quit.d.ts.map +1 -0
  121. package/dist/shell-quit.js +36 -0
  122. package/dist/shell-quit.js.map +1 -0
  123. package/dist/split-pane.d.ts +12 -1
  124. package/dist/split-pane.d.ts.map +1 -1
  125. package/dist/split-pane.js +31 -1
  126. package/dist/split-pane.js.map +1 -1
  127. package/dist/status-bar.d.ts +12 -0
  128. package/dist/status-bar.d.ts.map +1 -1
  129. package/dist/status-bar.js +45 -16
  130. package/dist/status-bar.js.map +1 -1
  131. package/dist/subapp/mount.d.ts.map +1 -1
  132. package/dist/subapp/mount.js +3 -0
  133. package/dist/subapp/mount.js.map +1 -1
  134. package/dist/surface-layout.d.ts +19 -0
  135. package/dist/surface-layout.d.ts.map +1 -0
  136. package/dist/surface-layout.js +87 -0
  137. package/dist/surface-layout.js.map +1 -0
  138. package/dist/transition-shaders.d.ts +10 -8
  139. package/dist/transition-shaders.d.ts.map +1 -1
  140. package/dist/transition-shaders.js +65 -19
  141. package/dist/transition-shaders.js.map +1 -1
  142. package/dist/types.d.ts +21 -7
  143. package/dist/types.d.ts.map +1 -1
  144. package/dist/types.js +11 -0
  145. package/dist/types.js.map +1 -1
  146. package/dist/view-output.d.ts +5 -4
  147. package/dist/view-output.d.ts.map +1 -1
  148. package/dist/view-output.js +37 -29
  149. package/dist/view-output.js.map +1 -1
  150. package/dist/viewport.d.ts +30 -1
  151. package/dist/viewport.d.ts.map +1 -1
  152. package/dist/viewport.js +77 -1
  153. package/dist/viewport.js.map +1 -1
  154. package/package.json +6 -3
  155. package/dist/layout-v3.d.ts +0 -10
  156. package/dist/layout-v3.d.ts.map +0 -1
  157. package/dist/layout-v3.js +0 -35
  158. package/dist/layout-v3.js.map +0 -1
package/LICENSE CHANGED
@@ -1,21 +1,159 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Flying Robots
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction, and
10
+ distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright
13
+ owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all other entities
16
+ that control, are controlled by, or are under common control with that entity.
17
+ For the purposes of this definition, "control" means (i) the power, direct or
18
+ indirect, to cause the direction or management of such entity, whether by
19
+ contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20
+ outstanding shares, or (iii) beneficial ownership of such entity.
21
+
22
+ "You" (or "Your") shall mean an individual or Legal Entity exercising
23
+ permissions granted by this License.
24
+
25
+ "Source" form shall mean the preferred form for making modifications, including
26
+ but not limited to software source code, documentation source, and configuration
27
+ files.
28
+
29
+ "Object" form shall mean any form resulting from mechanical transformation or
30
+ translation of a Source form, including but not limited to compiled object code,
31
+ generated documentation, and conversions to other media types.
32
+
33
+ "Work" shall mean the work of authorship, whether in Source or Object form, made
34
+ available under the License, as indicated by a copyright notice that is included
35
+ in or attached to the work (an example is provided in the Appendix below).
36
+
37
+ "Derivative Works" shall mean any work, whether in Source or Object form, that
38
+ is based on (or derived from) the Work and for which the editorial revisions,
39
+ annotations, elaborations, or other modifications represent, as a whole, an
40
+ original work of authorship. For the purposes of this License, Derivative Works
41
+ shall not include works that remain separable from, or merely link (or bind by
42
+ name) to the interfaces of, the Work and Derivative Works thereof.
43
+
44
+ "Contribution" shall mean any work of authorship, including the original version
45
+ of the Work and any modifications or additions to that Work or Derivative Works
46
+ thereof, that is intentionally submitted to Licensor for inclusion in the Work
47
+ by the copyright owner or by an individual or Legal Entity authorized to submit
48
+ on behalf of the copyright owner. For the purposes of this definition,
49
+ "submitted" means any form of electronic, verbal, or written communication sent
50
+ to the Licensor or its representatives, including but not limited to
51
+ communication on electronic mailing lists, source code control systems, and
52
+ issue tracking systems that are managed by, or on behalf of, the Licensor for
53
+ the purpose of discussing and improving the Work, but excluding communication
54
+ that is conspicuously marked or otherwise designated in writing by the copyright
55
+ owner as "Not a Contribution."
56
+
57
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58
+ of whom a Contribution has been received by Licensor and subsequently
59
+ incorporated within the Work.
60
+
61
+ 2. Grant of Copyright License. Subject to the terms and conditions of this
62
+ License, each Contributor hereby grants to You a perpetual, worldwide,
63
+ non-exclusive, no-charge, royalty-free, irrevocable copyright license to
64
+ reproduce, prepare Derivative Works of, publicly display, publicly perform,
65
+ sublicense, and distribute the Work and such Derivative Works in Source or
66
+ Object form.
67
+
68
+ 3. Grant of Patent License. Subject to the terms and conditions of this License,
69
+ each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
70
+ no-charge, royalty-free, irrevocable (except as stated in this section) patent
71
+ license to make, have made, use, offer to sell, sell, import, and otherwise
72
+ transfer the Work, where such license applies only to those patent claims
73
+ licensable by such Contributor that are necessarily infringed by their
74
+ Contribution(s) alone or by combination of their Contribution(s) with the Work
75
+ to which such Contribution(s) was submitted. If You institute patent litigation
76
+ against any entity (including a cross-claim or counterclaim in a lawsuit)
77
+ alleging that the Work or a Contribution incorporated within the Work
78
+ constitutes direct or contributory patent infringement, then any patent licenses
79
+ granted to You under this License for that Work shall terminate as of the date
80
+ such litigation is filed.
81
+
82
+ 4. Redistribution. You may reproduce and distribute copies of the Work or
83
+ Derivative Works thereof in any medium, with or without modifications, and in
84
+ Source or Object form, provided that You meet the following conditions:
85
+
86
+ (a) You must give any other recipients of the Work or Derivative Works a copy of
87
+ this License; and
88
+
89
+ (b) You must cause any modified files to carry prominent notices stating that You
90
+ changed the files; and
91
+
92
+ (c) You must retain, in the Source form of any Derivative Works that You
93
+ distribute, all copyright, patent, trademark, and attribution notices from the
94
+ Source form of the Work, excluding those notices that do not pertain to any part
95
+ of the Derivative Works; and
96
+
97
+ (d) If the Work includes a "NOTICE" text file as part of its distribution, then
98
+ any Derivative Works that You distribute must include a readable copy of the
99
+ attribution notices contained within such NOTICE file, excluding those notices
100
+ that do not pertain to any part of the Derivative Works, in at least one of the
101
+ following places: within a NOTICE text file distributed as part of the
102
+ Derivative Works; within the Source form or documentation, if provided along
103
+ with the Derivative Works; or, within a display generated by the Derivative
104
+ Works, if and wherever such third-party notices normally appear. The contents of
105
+ the NOTICE file are for informational purposes only and do not modify the
106
+ License. You may add Your own attribution notices within Derivative Works that
107
+ You distribute, alongside or as an addendum to the NOTICE text from the Work,
108
+ provided that such additional attribution notices cannot be construed as
109
+ modifying the License.
110
+
111
+ You may add Your own copyright statement to Your modifications and may provide
112
+ additional or different license terms and conditions for use, reproduction, or
113
+ distribution of Your modifications, or for any such Derivative Works as a whole,
114
+ provided Your use, reproduction, and distribution of the Work otherwise complies
115
+ with the conditions stated in this License.
116
+
117
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any
118
+ Contribution intentionally submitted for inclusion in the Work by You to the
119
+ Licensor shall be under the terms and conditions of this License, without any
120
+ additional terms or conditions. Notwithstanding the above, nothing herein shall
121
+ supersede or modify the terms of any separate license agreement you may have
122
+ executed with Licensor regarding such Contributions.
123
+
124
+ 6. Trademarks. This License does not grant permission to use the trade names,
125
+ trademarks, service marks, or product names of the Licensor, except as required
126
+ for reasonable and customary use in describing the origin of the Work and
127
+ reproducing the content of the NOTICE file.
128
+
129
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
130
+ writing, Licensor provides the Work (and each Contributor provides its
131
+ Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
132
+ KIND, either express or implied, including, without limitation, any warranties
133
+ or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
134
+ PARTICULAR PURPOSE. You are solely responsible for determining the
135
+ appropriateness of using or redistributing the Work and assume any risks
136
+ associated with Your exercise of permissions under this License.
137
+
138
+ 8. Limitation of Liability. In no event and under no legal theory, whether in
139
+ tort (including negligence), contract, or otherwise, unless required by
140
+ applicable law (such as deliberate and grossly negligent acts) or agreed to in
141
+ writing, shall any Contributor be liable to You for damages, including any
142
+ direct, indirect, special, incidental, or consequential damages of any
143
+ character arising as a result of this License or out of the use or inability to
144
+ use the Work (including but not limited to damages for loss of goodwill, work
145
+ stoppage, computer failure or malfunction, or any and all other commercial
146
+ damages or losses), even if such Contributor has been advised of the
147
+ possibility of such damages.
148
+
149
+ 9. Accepting Warranty or Additional Liability. While redistributing the Work or
150
+ Derivative Works thereof, You may choose to offer, and charge a fee for,
151
+ acceptance of support, warranty, indemnity, or other liability obligations
152
+ and/or rights consistent with this License. However, in accepting such
153
+ obligations, You may act only on Your own behalf and on Your sole
154
+ responsibility, not on behalf of any other Contributor, and only if You agree to
155
+ indemnify, defend, and hold each Contributor harmless for any liability incurred
156
+ by, or claims asserted against, such Contributor by reason of your accepting any
157
+ such warranty or additional liability.
158
+
159
+ END OF TERMS AND CONDITIONS
package/README.md CHANGED
@@ -4,27 +4,27 @@ The high-fidelity TEA runtime for Bijou.
4
4
 
5
5
  `bijou-tui` provides the application loop, layout primitives, motion, and orchestration needed to build complex interactive terminal apps on top of the Bijou core.
6
6
 
7
- ## V3.0.0 Evolution
7
+ ## Package Role in v4.0.0
8
8
 
9
- The TUI package has been completely overhauled in v3.0.0 to operate as a true graphics engine.
9
+ The TUI package is the surface-first fullscreen runtime in the Bijou stack.
10
10
 
11
- ### 🌟 What's New
12
- - **Honest view contract:** `App.view` and framed pane renderers now speak `ViewOutput` (`string | Surface | LayoutNode`). Strings still work, but they are the legacy compatibility path.
11
+ ### What's Included
12
+ - **Pure view contract:** `App.view` and framed pane renderers now speak `ViewOutput` (`Surface | LayoutNode`).
13
13
  - **Programmable Rendering Pipeline:** The TEA `view` output is now processed through a 5-stage middleware pipeline (`Layout -> Paint -> PostProcess -> Diff -> Output`). Add custom fragment shaders or logging middleware effortlessly.
14
14
  - **Fractal TEA (Sub-Apps):** Compose nested apps with `initSubApp()`, `updateSubApp()`, `mount()`, and `mapCmds()` instead of flattening everything into one update loop.
15
- - **Bijou CSS (BCSS):** Style supported V3 surface components and frame shell regions with type/class/id selectors, `var()` token lookups, and terminal-aware media queries (`@media (width < 80)`). This is not yet a global cascade across arbitrary layout nodes.
15
+ - **Bijou CSS (BCSS):** Style supported runtime surface components and frame shell regions with type/class/id selectors, `var()` token lookups, and terminal-aware media queries (`@media (width < 80)`). This is not yet a global cascade across arbitrary layout nodes.
16
16
  - **Declarative Motion:** Wrap any component in `motion({ key: 'id' }, ...)` and watch it smoothly interpolate layout changes (move, resize) using physics-based springs.
17
17
  - **Unified Heartbeat:** All animations and physics calculations are now synchronized to a single `PulseMsg`, eliminating timer jitter and saving CPU.
18
18
 
19
19
  ## Installation
20
20
 
21
21
  ```bash
22
- npm install @flyingrobots/bijou@3.0.0 @flyingrobots/bijou-node@3.0.0 @flyingrobots/bijou-tui@3.0.0
22
+ npm install @flyingrobots/bijou @flyingrobots/bijou-node @flyingrobots/bijou-tui
23
23
  ```
24
24
 
25
- If you are upgrading an existing app, see [`../../docs/MIGRATING_TO_V3.md`](../../docs/MIGRATING_TO_V3.md).
25
+ If you are upgrading an existing app, see [`../../docs/MIGRATING_TO_V4.md`](../../docs/MIGRATING_TO_V4.md).
26
26
 
27
- ## Quick Start (V3 Sub-App Composition)
27
+ ## Quick Start (Sub-App Composition)
28
28
 
29
29
  ```typescript
30
30
  import { initDefaultContext } from '@flyingrobots/bijou-node';
@@ -73,6 +73,7 @@ run(app);
73
73
 
74
74
  ```typescript
75
75
  import { initDefaultContext } from '@flyingrobots/bijou-node';
76
+ import { stringToSurface } from '@flyingrobots/bijou';
76
77
  import { run, quit, type App, isKeyMsg } from '@flyingrobots/bijou-tui';
77
78
 
78
79
  initDefaultContext();
@@ -91,7 +92,11 @@ const app: App<Model> = {
91
92
  return [model, []];
92
93
  },
93
94
 
94
- view: (model) => `Count: ${model.count}\n\nPress +/- to change, q to quit`,
95
+ view: (model) => {
96
+ const text = `Count: ${model.count}\n\nPress +/- to change, q to quit`;
97
+ const lines = text.split('\n');
98
+ return stringToSurface(text, Math.max(1, ...lines.map((line) => line.length)), lines.length);
99
+ },
95
100
  };
96
101
 
97
102
  run(app);
@@ -106,6 +111,17 @@ run(app);
106
111
 
107
112
  In non-interactive modes, there is no normal interactive event loop.
108
113
 
114
+ ## Command Model
115
+
116
+ `Cmd<M>` may resolve synchronously or asynchronously to:
117
+
118
+ - a final message
119
+ - `QUIT`
120
+ - a cleanup handle or cleanup function for a long-lived effect
121
+ - `void`
122
+
123
+ If a command installs a long-lived effect through `onPulse()` or another runtime-backed capability, return that cleanup so the event bus/runtime can dispose it on shutdown.
124
+
109
125
  ## Features Breakdown
110
126
 
111
127
  - **TEA runtime core**: deterministic model/update/view loop with command-driven side effects.
@@ -116,6 +132,37 @@ In non-interactive modes, there is no normal interactive event loop.
116
132
  - **App shell**: `createFramedApp()` for tabs/help/chrome/pane-focus boilerplate with optional command palette.
117
133
  - **Stateful building blocks**: navigable table, browsable list, file picker, focus area, and DAG pane with vim-friendly keymaps.
118
134
 
135
+ ## Choosing Component Families
136
+
137
+ ### Overlays and interruption
138
+
139
+ - Use `toast()` when you are composing a single transient overlay directly.
140
+ - Use the notification system when the app needs stacking, placement, actions, routing, or history.
141
+ - Use `drawer()` when the user should keep the main surface visible while working in supplemental detail.
142
+ - Use `modal()` when background shortcuts and pointer actions should be blocked.
143
+ - Use `tooltip()` only for tiny local explanation, not for decisions or scrollable content.
144
+ - If the overlay needs embedded component surfaces or multiple real rows, keep it on the structured `Surface` path with `compositeSurface()`.
145
+
146
+ ### Collection interaction
147
+
148
+ - Use core `table()` or `tableSurface()` for passive comparison.
149
+ - Use `navigableTable()` when row/cell focus and keyboard traversal are the real job.
150
+ - Use `browsableList()` when the content is one-dimensional and description-led rather than grid-oriented.
151
+ - Use `commandPaletteSurface()` when the outcome is an action or navigation target, not a stored form value.
152
+ - If users are really choosing persisted values, keep that work in core `select()` / `filter()` / `multiselect()` instead of turning the palette into a value picker.
153
+
154
+ ### Shell and workspace layout
155
+
156
+ - Use `createFramedApp()` when the app has multiple destinations, overlays, and workspace state that should be standardized.
157
+ - Use `splitPane()` when the user benefits from primary-versus-secondary context or side-by-side comparison.
158
+ - Use `grid()` when multiple stable regions deserve simultaneous visibility.
159
+ - Use `statusBarSurface()` when shell chrome already lives on the structured `Surface` path; keep `statusBar()` for explicit text output.
160
+ - Use `helpShortSurface()` or `helpViewSurface()` when shortcut guidance stays inside the rich shell; keep `helpShort()` / `helpView()` for explicit text output.
161
+ - Use `commandPaletteSurface()` for action discovery and navigation inside the shell, not as a substitute value picker.
162
+ - Use notifications for events and follow-up, not as a replacement for the status rail.
163
+ - Keep status rails concise and global; explanatory text belongs in the page, not in shell chrome.
164
+ - Mouse is enhancement, not baseline. Overlay layers should consume pointer input before shell chrome or page content, and every click target should mirror an existing keyboard path.
165
+
119
166
  ## Animation
120
167
 
121
168
  ### Spring Physics
@@ -174,6 +221,25 @@ const fired = tl.firedCallbacks(prev, tlState); // ['onReady']
174
221
 
175
222
  Position syntax: `'<'` (parallel), `'+=N'` (gap), `'-=N'` (overlap), `'<+=N'` (offset from previous start), absolute ms, `'label'`, `'label+=N'`.
176
223
 
224
+ ## Transition Shaders
225
+
226
+ Custom page transitions are surface-native in v4. Shader functions decide whether each cell shows the previous page or next page, and may optionally provide override data for that cell.
227
+
228
+ ```typescript
229
+ import { type TransitionShaderFn } from '@flyingrobots/bijou-tui';
230
+
231
+ const shimmer: TransitionShaderFn = ({ progress, x, width }) => {
232
+ const edge = Math.floor(progress * width);
233
+ if (x < edge) return { showNext: true };
234
+ if (x === edge) return { showNext: false, overrideChar: '░', overrideRole: 'marker' };
235
+ return { showNext: false };
236
+ };
237
+ ```
238
+
239
+ Use `overrideChar` when the base cell styling should stay intact, `overrideCell` when the shader needs full fg/bg/modifier control, and `overrideRole` to tell combinators whether an override is ambient (`'decoration'`) or positional (`'marker'`).
240
+
241
+ Use transition shaders to reinforce workspace change, not as default spectacle. If the effect makes the new page harder to read or hides state meaning that should remain explicit in static or accessible modes, it is the wrong transition.
242
+
177
243
  ## Layout
178
244
 
179
245
  ### Flexbox
@@ -200,32 +266,55 @@ Children can be **render functions** `(width, height) => string` — they receiv
200
266
  ### Viewport
201
267
 
202
268
  ```typescript
203
- import { viewport, createScrollState, scrollBy, pageDown } from '@flyingrobots/bijou-tui';
269
+ import { viewportSurface, createScrollStateForContent, scrollBy, pageDown } from '@flyingrobots/bijou-tui';
270
+ import { boxSurface } from '@flyingrobots/bijou';
204
271
 
205
- let scroll = createScrollState(content, viewportHeight);
272
+ const content = boxSurface(longText, { width: 72 });
273
+ let scroll = createScrollStateForContent(content, viewportHeight);
206
274
 
207
- // Render visible window with scrollbar
208
- const view = viewport({ width: 60, height: 20, content, scrollY: scroll.y });
275
+ // Mask the content to a visible window with scrollbar
276
+ const view = viewportSurface({ width: 60, height: 20, content, scrollY: scroll.y });
209
277
 
210
278
  // Handle scroll keys
211
279
  scroll = scrollBy(scroll, 1); // down one line
212
280
  scroll = pageDown(scroll); // down one page
213
281
  ```
214
282
 
283
+ Treat `viewportSurface()` as the canonical scroll mask for rich TUI composition. Keep `viewport()` for explicit text-lowering paths.
284
+
215
285
  ### Basic Layout
216
286
 
217
287
  ```typescript
218
- import { vstack, hstack } from '@flyingrobots/bijou-tui';
288
+ import {
289
+ hstack,
290
+ hstackSurface,
291
+ place,
292
+ placeSurface,
293
+ vstack,
294
+ vstackSurface,
295
+ } from '@flyingrobots/bijou-tui';
296
+
297
+ vstack(header, content, footer); // explicit text-lowering path
298
+ hstack(2, leftPanel, rightPanel); // explicit text-lowering path
299
+ place('Title', { width: 20, height: 3 }); // text placement
219
300
 
220
- vstack(header, content, footer); // vertical stack
221
- hstack(2, leftPanel, rightPanel); // side-by-side with gap
301
+ vstackSurface(headerSurface, bodySurface); // structured surface stack
302
+ hstackSurface(2, navSurface, mainSurface); // structured horizontal stack
303
+ placeSurface(dialogSurface, { // structured placement/alignment
304
+ width: cols,
305
+ height: rows,
306
+ hAlign: 'center',
307
+ vAlign: 'middle',
308
+ });
222
309
  ```
223
310
 
311
+ Prefer `vstackSurface()` / `hstackSurface()` / `placeSurface()` when the view is already composed from `Surface` values. Keep `vstack()` / `hstack()` / `place()` for explicit text composition or lowering paths.
312
+
224
313
  ### Split Pane
225
314
 
226
315
  ```typescript
227
316
  import {
228
- createSplitPaneState, splitPane, splitPaneResizeBy, splitPaneFocusNext,
317
+ createSplitPaneState, splitPaneSurface, splitPaneResizeBy, splitPaneFocusNext,
229
318
  } from '@flyingrobots/bijou-tui';
230
319
 
231
320
  let state = createSplitPaneState({ ratio: 0.35 });
@@ -235,7 +324,7 @@ state = splitPaneResizeBy(state, 2, { total: cols, minA: 16, minB: 16 });
235
324
  state = splitPaneFocusNext(state);
236
325
 
237
326
  // in view:
238
- const output = splitPane(state, {
327
+ const output = splitPaneSurface(state, {
239
328
  direction: 'row',
240
329
  width: cols,
241
330
  height: rows,
@@ -246,12 +335,14 @@ const output = splitPane(state, {
246
335
  });
247
336
  ```
248
337
 
338
+ Prefer `splitPaneSurface()` when the panes are already structured `Surface` views. Keep `splitPane()` for explicit text composition or lowering paths.
339
+
249
340
  ### Grid
250
341
 
251
342
  ```typescript
252
- import { grid } from '@flyingrobots/bijou-tui';
343
+ import { gridSurface } from '@flyingrobots/bijou-tui';
253
344
 
254
- const output = grid({
345
+ const output = gridSurface({
255
346
  width: cols,
256
347
  height: rows,
257
348
  columns: [24, '1fr'],
@@ -271,6 +362,8 @@ const output = grid({
271
362
  });
272
363
  ```
273
364
 
365
+ Prefer `gridSurface()` when the regions are already structured `Surface` views. Keep `grid()` for explicit text composition or lowering paths.
366
+
274
367
  ## Resize Handling
275
368
 
276
369
  Terminal resize events are dispatched automatically as `ResizeMsg`:
@@ -303,7 +396,7 @@ const bus = createEventBus<MyMsg>();
303
396
  bus.connectIO(ctx.io); // keyboard + resize
304
397
  bus.on((msg) => { /* ... */ }); // single subscription
305
398
  bus.emit(customMsg); // synthetic events
306
- bus.runCmd(someCommand); // command results re-emitted
399
+ bus.runCmd(someCommand); // final messages re-emitted, cleanup retained
307
400
  bus.dispose(); // clean shutdown
308
401
  ```
309
402
 
@@ -341,11 +434,21 @@ kb.enable('Quit');
341
434
  Auto-generate help text from registered bindings:
342
435
 
343
436
  ```typescript
344
- import { helpView, helpShort, helpFor } from '@flyingrobots/bijou-tui';
437
+ import {
438
+ helpView,
439
+ helpViewSurface,
440
+ helpShort,
441
+ helpShortSurface,
442
+ helpFor,
443
+ helpForSurface,
444
+ } from '@flyingrobots/bijou-tui';
345
445
 
346
- helpView(kb); // full grouped multi-line help
347
- helpShort(kb); // "q Quit • ? Help • Ctrl+c Force quit • j Down • k Up"
348
- helpFor(kb, 'Nav'); // only Navigation group
446
+ helpView(kb); // full grouped multi-line help
447
+ helpShort(kb); // "q Quit • ? Help • Ctrl+c Force quit • j Down • k Up"
448
+ helpFor(kb, 'Nav'); // only Navigation group
449
+ helpShortSurface(kb, { width: 48 }); // shell hint that stays on the Surface path
450
+ helpViewSurface(kb, { width: 48 }); // grouped help as a Surface
451
+ helpForSurface(kb, 'Nav', { width: 48 });
349
452
  ```
350
453
 
351
454
  ### Input Stack
@@ -377,7 +480,7 @@ stack.remove(modalId);
377
480
  Paint overlays (modals, toasts) on top of existing content:
378
481
 
379
482
  ```typescript
380
- import { composite, modal, toast } from '@flyingrobots/bijou-tui';
483
+ import { compositeSurface, modal, toast } from '@flyingrobots/bijou-tui';
381
484
 
382
485
  // Create a centered dialog
383
486
  const dialog = modal({
@@ -398,12 +501,17 @@ const notification = toast({
398
501
  });
399
502
 
400
503
  // Paint overlays onto background content
401
- const output = composite(backgroundView, [dialog, notification], { dim: true });
504
+ const output = compositeSurface(backgroundSurface, [dialog, notification], { dim: true });
402
505
  ```
403
506
 
404
- Each overlay is a `{ content, row, col }` object. `composite()` splices them onto the background using painter's algorithm (last overlay wins on overlap). The `dim` option fades the background with ANSI dim.
507
+ Each overlay now exposes both `surface` and `content` forms. Prefer `compositeSurface()` when your app is already on the surface-native path. Keep the string-oriented `composite()` path for explicit lowering boundaries, not as the default mental model. The `dim` option fades the background with ANSI dim.
508
+
509
+ `modal().body`, `modal().hint`, `drawer().content`, and `tooltip().content` accept either plain strings or structured `Surface` content. Use surfaces when the overlay needs real rows, embedded component surfaces, or richer composition inside the interrupting layer.
510
+
511
+ Reach for `toast()` when the app is composing a one-off overlay directly. Reach for the notification system when stacking, actions, routing, or history matter. The notification lab in `examples/notifications` is the canonical higher-level example.
405
512
 
406
513
  `drawer()` now supports `left`/`right`/`top`/`bottom` anchors and optional `region` mounting for panel-scoped overlays.
514
+ Use `drawer()` when the user should keep the main task visible while consulting or editing supplemental context. Use `modal()` when the user must stop and decide. Use `tooltip()` only for tiny local explanation, not for commands or scrollable content.
407
515
 
408
516
  ## App Frame
409
517
 
@@ -414,10 +522,23 @@ Each overlay is a `{ content, row, col }` object. `composite()` splices them ont
414
522
  - frame help (`?`) and optional command palette (`ctrl+p` / `:`)
415
523
  - overlay factory with pane rects for panel-scoped drawers/modals
416
524
 
417
- Pane renderers may return a legacy string, a `Surface`, or a `LayoutNode`. The shell normalizes those outputs into the framed scroll/focus path for you.
525
+ Pane renderers return a `Surface` or a `LayoutNode`. The shell normalizes those outputs into the framed scroll/focus path for you.
526
+
527
+ Typed shell notes:
528
+
529
+ - `createFramedApp<PageModel, Msg>()` returns `FramedApp<PageModel, Msg>`
530
+ - page `update()` receives `FramePageMsg<Msg>` so raw `mouse` / `pulse` delivery is explicit
531
+ - wrapped shell commands carry `FramedAppMsg<Msg>` instead of collapsing frame/page wrappers into plain `Msg`
418
532
 
419
533
  See `examples/release-workbench/main.ts` for the canonical shell demo and `examples/app-frame/main.ts` for a compact focused example.
420
534
 
535
+ Shell role split matters:
536
+
537
+ - `statusBarSurface()` communicates concise global state
538
+ - `helpShortSurface()` / `helpViewSurface()` teach shortcuts and scope
539
+ - `commandPaletteSurface()` handles action discovery and navigation
540
+ - notifications surface events and follow-up
541
+
421
542
  ## Building Blocks
422
543
 
423
544
  Reusable stateful components that follow the TEA state + pure transformers + sync render + convenience keymap pattern:
@@ -426,55 +547,100 @@ Reusable stateful components that follow the TEA state + pure transformers + syn
426
547
 
427
548
  ```typescript
428
549
  import {
429
- createNavigableTableState, navigableTable, navTableFocusNext,
550
+ createNavigableTableState, navigableTable, navigableTableSurface, navTableFocusNext,
430
551
  navTableKeyMap, helpShort,
431
552
  } from '@flyingrobots/bijou-tui';
432
553
 
433
554
  const state = createNavigableTableState({ columns, rows, height: 10 });
434
- const output = navigableTable(state, { ctx });
555
+ const textOutput = navigableTable(state, { ctx });
556
+ const surfaceOutput = navigableTableSurface(state, { ctx });
435
557
  const next = navTableFocusNext(state);
436
558
  ```
437
559
 
560
+ Use `navigableTableSurface()` when the user should actively traverse a table inside a rich TUI surface. Keep `navigableTable()` for explicit text lowering. If the job is still passive comparison, prefer core `table()` or `tableSurface()` and keep the interaction layer simpler.
561
+
438
562
  ### Browsable List
439
563
 
440
564
  ```typescript
441
565
  import {
442
- createBrowsableListState, browsableList, listFocusNext,
566
+ createBrowsableListState, browsableList, browsableListSurface, listFocusNext,
443
567
  browsableListKeyMap,
444
568
  } from '@flyingrobots/bijou-tui';
445
569
 
446
570
  const state = createBrowsableListState({ items, height: 10 });
447
- const output = browsableList(state);
571
+ const textOutput = browsableList(state);
572
+ const surfaceOutput = browsableListSurface(state, { width: 40 });
448
573
  ```
449
574
 
575
+ Use `browsableListSurface()` when the list belongs inside a rich TUI region and should share viewport masking semantics with pagers and focus areas. Keep `browsableList()` for explicit text lowering.
576
+
450
577
  ### File Picker
451
578
 
452
579
  ```typescript
453
580
  import {
454
- createFilePickerState, filePicker, fpFocusNext, fpEnter, fpBack,
581
+ createFilePickerState, filePicker, filePickerSurface, fpFocusNext, fpEnter, fpBack,
455
582
  filePickerKeyMap,
456
583
  } from '@flyingrobots/bijou-tui';
457
584
  import { nodeIO } from '@flyingrobots/bijou-node';
458
585
 
459
586
  const io = nodeIO();
460
587
  const state = createFilePickerState({ cwd: process.cwd(), io, height: 15 });
461
- const output = filePicker(state);
588
+ const textOutput = filePicker(state);
589
+ const surfaceOutput = filePickerSurface(state, { width: 60 });
590
+ ```
591
+
592
+ Use `filePickerSurface()` when the browser lives inside a rich TUI pane and should inherit shared viewport masking semantics. Keep `filePicker()` for explicit text lowering.
593
+
594
+ ### Command Palette
595
+
596
+ ```typescript
597
+ import {
598
+ createCommandPaletteState, commandPalette, commandPaletteSurface,
599
+ cpFilter, commandPaletteKeyMap,
600
+ } from '@flyingrobots/bijou-tui';
601
+
602
+ const state = createCommandPaletteState(items, 8);
603
+ const textOutput = commandPalette(state, { width: 60 });
604
+ const surfaceOutput = commandPaletteSurface(state, { width: 60, ctx });
605
+ ```
606
+
607
+ Use `commandPaletteSurface()` when the palette is part of a structured shell or overlay and should share viewport masking semantics. Keep `commandPalette()` for explicit text lowering.
608
+
609
+ ### Pager
610
+
611
+ ```typescript
612
+ import {
613
+ createPagerStateForSurface,
614
+ pagerSurface,
615
+ pagerScrollBy,
616
+ } from '@flyingrobots/bijou-tui';
617
+
618
+ const state = createPagerStateForSurface(contentSurface, { width: 60, height: 20 });
619
+ const output = pagerSurface(contentSurface, state);
462
620
  ```
463
621
 
464
622
  ### Focus Area
465
623
 
466
624
  ```typescript
467
625
  import {
468
- createFocusAreaState, focusArea, focusAreaScrollBy,
626
+ createFocusAreaStateForSurface, focusAreaScrollBy, focusAreaSurface,
469
627
  focusAreaKeyMap,
470
628
  } from '@flyingrobots/bijou-tui';
471
629
 
472
- const state = createFocusAreaState({ content, width: 60, height: 20, overflowX: 'scroll' });
473
- const output = focusArea(state, { focused: true, ctx });
630
+ const state = createFocusAreaStateForSurface(contentSurface, {
631
+ width: 60,
632
+ height: 20,
633
+ overflowX: 'scroll',
634
+ });
635
+ const output = focusAreaSurface(contentSurface, state, { focused: true, ctx });
474
636
  ```
475
637
 
638
+ If the pane is still intentionally text-composed, `createFocusAreaState()` + `focusArea()` remain the explicit lowering path.
639
+
476
640
  ### DAG Pane
477
641
 
642
+ Use `dagPane()` when graph inspection is an active task and the user needs keyboard-owned selection, path highlighting, and scroll control. Keep plain `dag()` in `@flyingrobots/bijou` for passive graph explanation, and move to `dagSlice()` or `dagStats()` when a focused fragment or structural summary would be more honest than a full interactive graph.
643
+
478
644
  ```typescript
479
645
  import {
480
646
  createDagPaneState, dagPane, dagPaneSelectChild,
@@ -495,7 +661,7 @@ All building blocks include `*KeyMap()` factories for preconfigured vim-style ke
495
661
 
496
662
  ## License
497
663
 
498
- MIT
664
+ Apache-2.0
499
665
 
500
666
  ---
501
667
 
@@ -5,19 +5,19 @@
5
5
  * page frame state sync, and transition tick command.
6
6
  */
7
7
  import type { FramePage, CreateFramedAppOptions } from './app-frame.js';
8
- import type { InternalFrameModel, FrameAction } from './app-frame-types.js';
8
+ import type { InternalFrameModel, FrameAction, FramedAppMsg } from './app-frame-types.js';
9
9
  import type { Cmd } from './types.js';
10
10
  import type { DockDirection } from './panel-dock.js';
11
11
  /** Dispatch a frame-level action (tab switch, pane cycle, scroll, palette, help toggle, transitions). */
12
- export declare function applyFrameAction<PageModel, Msg>(action: FrameAction, model: InternalFrameModel<PageModel, Msg>, options: CreateFramedAppOptions<PageModel, Msg>, pagesById: Map<string, FramePage<PageModel, Msg>>): [InternalFrameModel<PageModel, Msg>, Cmd<Msg>[]];
12
+ export declare function applyFrameAction<PageModel, Msg>(action: FrameAction, model: InternalFrameModel<PageModel, Msg>, options: CreateFramedAppOptions<PageModel, Msg>, pagesById: Map<string, FramePage<PageModel, Msg>>): [InternalFrameModel<PageModel, Msg>, Cmd<FramedAppMsg<Msg>>[]];
13
13
  /** Cycle the active tab by `delta` positions, optionally starting a transition. */
14
- export declare function switchTab<PageModel, Msg>(model: InternalFrameModel<PageModel, Msg>, delta: number, pagesById: Map<string, FramePage<PageModel, Msg>>, options: CreateFramedAppOptions<PageModel, Msg>): [InternalFrameModel<PageModel, Msg>, Cmd<Msg>[]];
14
+ export declare function switchTab<PageModel, Msg>(model: InternalFrameModel<PageModel, Msg>, delta: number, pagesById: Map<string, FramePage<PageModel, Msg>>, options: CreateFramedAppOptions<PageModel, Msg>): [InternalFrameModel<PageModel, Msg>, Cmd<FramedAppMsg<Msg>>[]];
15
15
  /** Move focus to the next or previous pane in the active page's layout. */
16
16
  export declare function cyclePane<PageModel, Msg>(model: InternalFrameModel<PageModel, Msg>, delta: number, pagesById: Map<string, FramePage<PageModel, Msg>>): InternalFrameModel<PageModel, Msg>;
17
17
  /** Apply a scroll action to the currently focused pane. */
18
18
  export declare function scrollFocusedPane<PageModel, Msg>(model: InternalFrameModel<PageModel, Msg>, action: Extract<FrameAction, {
19
19
  type: 'scroll-up' | 'scroll-down' | 'page-up' | 'page-down' | 'top' | 'bottom' | 'scroll-left' | 'scroll-right';
20
- }>, pagesById: Map<string, FramePage<PageModel, Msg>>): InternalFrameModel<PageModel, Msg>;
20
+ }>, pagesById: Map<string, FramePage<PageModel, Msg>>, options: CreateFramedAppOptions<PageModel, Msg>): InternalFrameModel<PageModel, Msg>;
21
21
  /** Toggle minimize on the focused pane of the active page. */
22
22
  export declare function applyToggleMinimize<PageModel, Msg>(model: InternalFrameModel<PageModel, Msg>, pagesById: Map<string, FramePage<PageModel, Msg>>): InternalFrameModel<PageModel, Msg>;
23
23
  /** Toggle maximize on the focused pane of the active page. */
@@ -29,5 +29,5 @@ export declare function syncPageFrameState<PageModel, Msg>(model: InternalFrameM
29
29
  /**
30
30
  * Create a TEA command that drives transition re-renders from the shared pulse.
31
31
  */
32
- export declare function createTransitionTickCmd<Msg>(durationMs: number, generation: number): Cmd<Msg>;
32
+ export declare function createTransitionTickCmd<Msg>(durationMs: number, generation: number): Cmd<FramedAppMsg<Msg>>;
33
33
  //# sourceMappingURL=app-frame-actions.d.ts.map