tuiuiu.js 1.0.4 → 1.0.6-next.4ff3fbc

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 (292) hide show
  1. package/dist/app/render-loop.d.ts +2 -0
  2. package/dist/app/render-loop.d.ts.map +1 -1
  3. package/dist/app/render-loop.js +14 -3
  4. package/dist/app/render-loop.js.map +1 -1
  5. package/dist/atoms/button.d.ts +1 -1
  6. package/dist/atoms/button.d.ts.map +1 -1
  7. package/dist/atoms/button.js +13 -11
  8. package/dist/atoms/button.js.map +1 -1
  9. package/dist/atoms/progress-bar.d.ts.map +1 -1
  10. package/dist/atoms/progress-bar.js +15 -12
  11. package/dist/atoms/progress-bar.js.map +1 -1
  12. package/dist/atoms/scrollbar.d.ts +30 -0
  13. package/dist/atoms/scrollbar.d.ts.map +1 -0
  14. package/dist/atoms/scrollbar.js +46 -0
  15. package/dist/atoms/scrollbar.js.map +1 -0
  16. package/dist/atoms/slider.js +6 -6
  17. package/dist/atoms/slider.js.map +1 -1
  18. package/dist/atoms/spinner.d.ts.map +1 -1
  19. package/dist/atoms/spinner.js +3 -2
  20. package/dist/atoms/spinner.js.map +1 -1
  21. package/dist/atoms/switch.d.ts.map +1 -1
  22. package/dist/atoms/switch.js +3 -2
  23. package/dist/atoms/switch.js.map +1 -1
  24. package/dist/atoms/text-input.d.ts.map +1 -1
  25. package/dist/atoms/text-input.js +6 -5
  26. package/dist/atoms/text-input.js.map +1 -1
  27. package/dist/atoms/timer.d.ts +1 -1
  28. package/dist/atoms/timer.js +4 -4
  29. package/dist/atoms/timer.js.map +1 -1
  30. package/dist/atoms/tooltip.d.ts +4 -4
  31. package/dist/atoms/tooltip.js +19 -19
  32. package/dist/atoms/tooltip.js.map +1 -1
  33. package/dist/core/delta-render.d.ts.map +1 -1
  34. package/dist/core/delta-render.js +17 -12
  35. package/dist/core/delta-render.js.map +1 -1
  36. package/dist/core/index.d.ts +2 -2
  37. package/dist/core/index.d.ts.map +1 -1
  38. package/dist/core/index.js +15 -3
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/renderer.d.ts +4 -2
  41. package/dist/core/renderer.d.ts.map +1 -1
  42. package/dist/core/renderer.js +70 -43
  43. package/dist/core/renderer.js.map +1 -1
  44. package/dist/core/theme-loader.d.ts +89 -0
  45. package/dist/core/theme-loader.d.ts.map +1 -0
  46. package/dist/core/theme-loader.js +228 -0
  47. package/dist/core/theme-loader.js.map +1 -0
  48. package/dist/core/theme-types.d.ts +577 -0
  49. package/dist/core/theme-types.d.ts.map +1 -0
  50. package/dist/core/theme-types.js +13 -0
  51. package/dist/core/theme-types.js.map +1 -0
  52. package/dist/core/theme.d.ts +155 -236
  53. package/dist/core/theme.d.ts.map +1 -1
  54. package/dist/core/theme.js +418 -709
  55. package/dist/core/theme.js.map +1 -1
  56. package/dist/design-system/core/renderer.d.ts +14 -3
  57. package/dist/design-system/core/renderer.d.ts.map +1 -1
  58. package/dist/design-system/core/renderer.js +77 -38
  59. package/dist/design-system/core/renderer.js.map +1 -1
  60. package/dist/design-system/feedback/badge.d.ts +2 -8
  61. package/dist/design-system/feedback/badge.d.ts.map +1 -1
  62. package/dist/design-system/feedback/badge.js +32 -8
  63. package/dist/design-system/feedback/badge.js.map +1 -1
  64. package/dist/design-system/feedback/progress-bar.d.ts.map +1 -1
  65. package/dist/design-system/feedback/progress-bar.js +15 -12
  66. package/dist/design-system/feedback/progress-bar.js.map +1 -1
  67. package/dist/design-system/feedback/spinner.d.ts.map +1 -1
  68. package/dist/design-system/feedback/spinner.js +4 -3
  69. package/dist/design-system/feedback/spinner.js.map +1 -1
  70. package/dist/design-system/feedback/timer.js +3 -3
  71. package/dist/design-system/feedback/timer.js.map +1 -1
  72. package/dist/design-system/layout/app.js +6 -6
  73. package/dist/design-system/layout/app.js.map +1 -1
  74. package/dist/design-system/navigation/file-manager.js +15 -15
  75. package/dist/design-system/navigation/file-manager.js.map +1 -1
  76. package/dist/design-system/visual/big-text.js +6 -6
  77. package/dist/design-system/visual/big-text.js.map +1 -1
  78. package/dist/design-system/visual/digits.js +7 -7
  79. package/dist/design-system/visual/digits.js.map +1 -1
  80. package/dist/design-system/visual/splash-screen.d.ts.map +1 -1
  81. package/dist/design-system/visual/splash-screen.js +23 -17
  82. package/dist/design-system/visual/splash-screen.js.map +1 -1
  83. package/dist/design-system/visual/tooltip.js +16 -16
  84. package/dist/design-system/visual/tooltip.js.map +1 -1
  85. package/dist/hooks/index.d.ts +1 -1
  86. package/dist/hooks/index.d.ts.map +1 -1
  87. package/dist/hooks/index.js +1 -1
  88. package/dist/hooks/index.js.map +1 -1
  89. package/dist/hooks/types.d.ts +2 -0
  90. package/dist/hooks/types.d.ts.map +1 -1
  91. package/dist/hooks/use-app.d.ts +5 -0
  92. package/dist/hooks/use-app.d.ts.map +1 -1
  93. package/dist/hooks/use-app.js +10 -0
  94. package/dist/hooks/use-app.js.map +1 -1
  95. package/dist/index.d.ts +3 -3
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +11 -3
  98. package/dist/index.js.map +1 -1
  99. package/dist/mcp/docs-data.d.ts.map +1 -1
  100. package/dist/mcp/docs-data.js +625 -28
  101. package/dist/mcp/docs-data.js.map +1 -1
  102. package/dist/mcp/server.js +1 -1
  103. package/dist/molecules/autocomplete.js +10 -10
  104. package/dist/molecules/autocomplete.js.map +1 -1
  105. package/dist/molecules/calendar.d.ts.map +1 -1
  106. package/dist/molecules/calendar.js +13 -12
  107. package/dist/molecules/calendar.js.map +1 -1
  108. package/dist/molecules/code-block.js +21 -21
  109. package/dist/molecules/code-block.js.map +1 -1
  110. package/dist/molecules/collapsible.d.ts +9 -5
  111. package/dist/molecules/collapsible.d.ts.map +1 -1
  112. package/dist/molecules/collapsible.js +39 -7
  113. package/dist/molecules/collapsible.js.map +1 -1
  114. package/dist/molecules/data-viz/bar-chart.js +8 -8
  115. package/dist/molecules/data-viz/bar-chart.js.map +1 -1
  116. package/dist/molecules/data-viz/gauge.d.ts.map +1 -1
  117. package/dist/molecules/data-viz/gauge.js +17 -17
  118. package/dist/molecules/data-viz/gauge.js.map +1 -1
  119. package/dist/molecules/data-viz/heatmap.js +11 -11
  120. package/dist/molecules/data-viz/heatmap.js.map +1 -1
  121. package/dist/molecules/data-viz/legend.js +3 -3
  122. package/dist/molecules/data-viz/legend.js.map +1 -1
  123. package/dist/molecules/data-viz/line-chart.js +4 -4
  124. package/dist/molecules/data-viz/line-chart.js.map +1 -1
  125. package/dist/molecules/data-viz/sparkline.js +1 -1
  126. package/dist/molecules/data-viz/sparkline.js.map +1 -1
  127. package/dist/molecules/markdown.js +23 -23
  128. package/dist/molecules/markdown.js.map +1 -1
  129. package/dist/molecules/multi-select.js +9 -9
  130. package/dist/molecules/multi-select.js.map +1 -1
  131. package/dist/molecules/radio-group.js +8 -8
  132. package/dist/molecules/radio-group.js.map +1 -1
  133. package/dist/molecules/select.d.ts.map +1 -1
  134. package/dist/molecules/select.js +15 -13
  135. package/dist/molecules/select.js.map +1 -1
  136. package/dist/molecules/table.js +3 -3
  137. package/dist/molecules/table.js.map +1 -1
  138. package/dist/molecules/tabs.d.ts +6 -2
  139. package/dist/molecules/tabs.d.ts.map +1 -1
  140. package/dist/molecules/tabs.js +25 -13
  141. package/dist/molecules/tabs.js.map +1 -1
  142. package/dist/molecules/tree.js +5 -5
  143. package/dist/molecules/tree.js.map +1 -1
  144. package/dist/organisms/command-palette.d.ts.map +1 -1
  145. package/dist/organisms/command-palette.js +19 -17
  146. package/dist/organisms/command-palette.js.map +1 -1
  147. package/dist/organisms/data-table.js +5 -5
  148. package/dist/organisms/data-table.js.map +1 -1
  149. package/dist/organisms/file-browser.js +15 -15
  150. package/dist/organisms/file-browser.js.map +1 -1
  151. package/dist/organisms/index.d.ts +2 -1
  152. package/dist/organisms/index.d.ts.map +1 -1
  153. package/dist/organisms/index.js +13 -1
  154. package/dist/organisms/index.js.map +1 -1
  155. package/dist/organisms/modal.d.ts +46 -0
  156. package/dist/organisms/modal.d.ts.map +1 -1
  157. package/dist/organisms/modal.js +110 -29
  158. package/dist/organisms/modal.js.map +1 -1
  159. package/dist/organisms/scroll-area.js +6 -6
  160. package/dist/organisms/scroll-area.js.map +1 -1
  161. package/dist/organisms/scroll-list.d.ts +168 -0
  162. package/dist/organisms/scroll-list.d.ts.map +1 -0
  163. package/dist/organisms/scroll-list.js +338 -0
  164. package/dist/organisms/scroll-list.js.map +1 -0
  165. package/dist/organisms/split-panel.d.ts.map +1 -1
  166. package/dist/organisms/split-panel.js +7 -4
  167. package/dist/organisms/split-panel.js.map +1 -1
  168. package/dist/primitives/divider.d.ts.map +1 -1
  169. package/dist/primitives/divider.js +4 -3
  170. package/dist/primitives/divider.js.map +1 -1
  171. package/dist/primitives/index.d.ts +1 -0
  172. package/dist/primitives/index.d.ts.map +1 -1
  173. package/dist/primitives/index.js +1 -0
  174. package/dist/primitives/index.js.map +1 -1
  175. package/dist/primitives/nodes.d.ts.map +1 -1
  176. package/dist/primitives/nodes.js +5 -3
  177. package/dist/primitives/nodes.js.map +1 -1
  178. package/dist/primitives/scroll.d.ts +127 -0
  179. package/dist/primitives/scroll.d.ts.map +1 -0
  180. package/dist/primitives/scroll.js +202 -0
  181. package/dist/primitives/scroll.js.map +1 -0
  182. package/dist/storybook/app.d.ts.map +1 -1
  183. package/dist/storybook/app.js +218 -84
  184. package/dist/storybook/app.js.map +1 -1
  185. package/dist/storybook/components/compare-view.d.ts.map +1 -1
  186. package/dist/storybook/components/compare-view.js +25 -19
  187. package/dist/storybook/components/compare-view.js.map +1 -1
  188. package/dist/storybook/components/hotkeys-panel.d.ts +12 -0
  189. package/dist/storybook/components/hotkeys-panel.d.ts.map +1 -0
  190. package/dist/storybook/components/hotkeys-panel.js +44 -0
  191. package/dist/storybook/components/hotkeys-panel.js.map +1 -0
  192. package/dist/storybook/components/index.d.ts +6 -0
  193. package/dist/storybook/components/index.d.ts.map +1 -1
  194. package/dist/storybook/components/index.js +9 -0
  195. package/dist/storybook/components/index.js.map +1 -1
  196. package/dist/storybook/components/log-viewer.d.ts +9 -4
  197. package/dist/storybook/components/log-viewer.d.ts.map +1 -1
  198. package/dist/storybook/components/log-viewer.js +97 -35
  199. package/dist/storybook/components/log-viewer.js.map +1 -1
  200. package/dist/storybook/components/navbar.d.ts +17 -0
  201. package/dist/storybook/components/navbar.d.ts.map +1 -0
  202. package/dist/storybook/components/navbar.js +52 -0
  203. package/dist/storybook/components/navbar.js.map +1 -0
  204. package/dist/storybook/components/pressed-keys.d.ts +29 -0
  205. package/dist/storybook/components/pressed-keys.d.ts.map +1 -0
  206. package/dist/storybook/components/pressed-keys.js +148 -0
  207. package/dist/storybook/components/pressed-keys.js.map +1 -0
  208. package/dist/storybook/components/preview.d.ts.map +1 -1
  209. package/dist/storybook/components/preview.js +44 -34
  210. package/dist/storybook/components/preview.js.map +1 -1
  211. package/dist/storybook/components/search-bar.d.ts +29 -0
  212. package/dist/storybook/components/search-bar.d.ts.map +1 -0
  213. package/dist/storybook/components/search-bar.js +101 -0
  214. package/dist/storybook/components/search-bar.js.map +1 -0
  215. package/dist/storybook/components/sidebar.d.ts.map +1 -1
  216. package/dist/storybook/components/sidebar.js +27 -21
  217. package/dist/storybook/components/sidebar.js.map +1 -1
  218. package/dist/storybook/components/status-bar.d.ts +22 -0
  219. package/dist/storybook/components/status-bar.d.ts.map +1 -0
  220. package/dist/storybook/components/status-bar.js +64 -0
  221. package/dist/storybook/components/status-bar.js.map +1 -0
  222. package/dist/storybook/store.d.ts +13 -1
  223. package/dist/storybook/store.d.ts.map +1 -1
  224. package/dist/storybook/store.js +21 -1
  225. package/dist/storybook/store.js.map +1 -1
  226. package/dist/storybook/stories/apps/index.d.ts.map +1 -1
  227. package/dist/storybook/stories/apps/index.js +82 -147
  228. package/dist/storybook/stories/apps/index.js.map +1 -1
  229. package/dist/storybook/stories/atoms/index.d.ts.map +1 -1
  230. package/dist/storybook/stories/atoms/index.js +57 -52
  231. package/dist/storybook/stories/atoms/index.js.map +1 -1
  232. package/dist/storybook/stories/molecules/index.d.ts +16 -0
  233. package/dist/storybook/stories/molecules/index.d.ts.map +1 -1
  234. package/dist/storybook/stories/molecules/index.js +299 -91
  235. package/dist/storybook/stories/molecules/index.js.map +1 -1
  236. package/dist/storybook/stories/organisms/index.d.ts.map +1 -1
  237. package/dist/storybook/stories/organisms/index.js +207 -120
  238. package/dist/storybook/stories/organisms/index.js.map +1 -1
  239. package/dist/templates/app.d.ts +28 -23
  240. package/dist/templates/app.d.ts.map +1 -1
  241. package/dist/templates/app.js +104 -17
  242. package/dist/templates/app.js.map +1 -1
  243. package/dist/themes/catppuccin.theme.d.ts +8 -0
  244. package/dist/themes/catppuccin.theme.d.ts.map +1 -0
  245. package/dist/themes/catppuccin.theme.js +538 -0
  246. package/dist/themes/catppuccin.theme.js.map +1 -0
  247. package/dist/themes/dark.theme.d.ts +8 -0
  248. package/dist/themes/dark.theme.d.ts.map +1 -0
  249. package/dist/themes/dark.theme.js +502 -0
  250. package/dist/themes/dark.theme.js.map +1 -0
  251. package/dist/themes/dracula.theme.d.ts +8 -0
  252. package/dist/themes/dracula.theme.d.ts.map +1 -0
  253. package/dist/themes/dracula.theme.js +518 -0
  254. package/dist/themes/dracula.theme.js.map +1 -0
  255. package/dist/themes/gruvbox.theme.d.ts +8 -0
  256. package/dist/themes/gruvbox.theme.d.ts.map +1 -0
  257. package/dist/themes/gruvbox.theme.js +529 -0
  258. package/dist/themes/gruvbox.theme.js.map +1 -0
  259. package/dist/themes/high-contrast-dark.theme.d.ts +8 -0
  260. package/dist/themes/high-contrast-dark.theme.d.ts.map +1 -0
  261. package/dist/themes/high-contrast-dark.theme.js +503 -0
  262. package/dist/themes/high-contrast-dark.theme.js.map +1 -0
  263. package/dist/themes/index.d.ts +58 -0
  264. package/dist/themes/index.d.ts.map +1 -0
  265. package/dist/themes/index.js +75 -0
  266. package/dist/themes/index.js.map +1 -0
  267. package/dist/themes/light.theme.d.ts +8 -0
  268. package/dist/themes/light.theme.d.ts.map +1 -0
  269. package/dist/themes/light.theme.js +502 -0
  270. package/dist/themes/light.theme.js.map +1 -0
  271. package/dist/themes/monochrome.theme.d.ts +8 -0
  272. package/dist/themes/monochrome.theme.d.ts.map +1 -0
  273. package/dist/themes/monochrome.theme.js +503 -0
  274. package/dist/themes/monochrome.theme.js.map +1 -0
  275. package/dist/themes/monokai.theme.d.ts +8 -0
  276. package/dist/themes/monokai.theme.d.ts.map +1 -0
  277. package/dist/themes/monokai.theme.js +518 -0
  278. package/dist/themes/monokai.theme.js.map +1 -0
  279. package/dist/themes/nord.theme.d.ts +8 -0
  280. package/dist/themes/nord.theme.d.ts.map +1 -0
  281. package/dist/themes/nord.theme.js +527 -0
  282. package/dist/themes/nord.theme.js.map +1 -0
  283. package/dist/themes/solarized-dark.theme.d.ts +8 -0
  284. package/dist/themes/solarized-dark.theme.d.ts.map +1 -0
  285. package/dist/themes/solarized-dark.theme.js +525 -0
  286. package/dist/themes/solarized-dark.theme.js.map +1 -0
  287. package/dist/themes/tokyo-night.theme.d.ts +8 -0
  288. package/dist/themes/tokyo-night.theme.d.ts.map +1 -0
  289. package/dist/themes/tokyo-night.theme.js +529 -0
  290. package/dist/themes/tokyo-night.theme.js.map +1 -0
  291. package/dist/version.js +1 -1
  292. package/package.json +1 -1
@@ -9,13 +9,16 @@ import { render } from '../app/render-loop.js';
9
9
  import { useState, useInput, useApp, useEffect } from '../hooks/index.js';
10
10
  import { createRef } from '../core/index.js';
11
11
  import { startTick, stopTick, getTick, isTickRunning, setTickRate } from '../core/tick.js';
12
- import { setTheme, useTheme, getNextTheme, themeColor } from '../core/theme.js';
12
+ import { setTheme, useTheme, getNextTheme, getTheme } from '../core/theme.js';
13
13
  import { allStories } from './stories/index.js';
14
14
  import { COLORS, TUIUIU_BIRD_COLORED } from './data/ascii-art.js';
15
15
  import { SplashScreen, createSplashScreen } from '../design-system/visual/splash-screen.js';
16
+ // Version
17
+ import { getVersion, getVersionSync } from '../version.js';
16
18
  // Storybook Global State & Logger
17
19
  import { storybookStore, interceptConsole } from './store.js';
18
- import { LogViewer } from './components/log-viewer.js';
20
+ // Storybook UI Components
21
+ import { Navbar, MetricsStatusBar, SearchBar, searchStories, ConsoleAccordion, HotkeysPanel, PressedKeysIndicator, recordKeyPress, clearOldKeyPresses, } from './components/index.js';
19
22
  // =============================================================================
20
23
  // Initialization
21
24
  // =============================================================================
@@ -41,19 +44,21 @@ function getStoriesByCategory(category) {
41
44
  * Render story content safely
42
45
  */
43
46
  function StoryContent(props) {
47
+ const theme = getTheme();
44
48
  const { story, values, frame = 0 } = props;
45
49
  try {
46
50
  return story.render(values, frame);
47
51
  }
48
52
  catch (error) {
49
53
  console.error('Render error:', error); // Captured by our interceptor!
50
- return Box({ flexDirection: 'column', padding: 1, borderStyle: 'single', borderColor: themeColor('error') }, Text({ color: themeColor('error'), bold: true }, 'Render Error'), Text({ color: themeColor('error') }, error.message || String(error)));
54
+ return Box({ flexDirection: 'column', padding: 1, borderStyle: 'single', borderColor: theme.accents.critical }, Text({ color: theme.accents.critical, bold: true }, 'Render Error'), Text({ color: theme.accents.critical }, error.message || String(error)));
51
55
  }
52
56
  }
53
57
  /**
54
58
  * Sidebar component - category and story list with virtual scroll
55
59
  */
56
60
  function Sidebar(props) {
61
+ const theme = getTheme();
57
62
  const { categories, currentCategory, stories, selectedIndex, focusArea, onCategoryClick, onFocus } = props;
58
63
  const isFocused = focusArea === 'sidebar';
59
64
  // Virtual scroll: show window around selected item
@@ -81,12 +86,12 @@ function Sidebar(props) {
81
86
  flexDirection: 'column',
82
87
  width: 50,
83
88
  borderStyle: 'single',
84
- borderColor: isFocused ? themeColor('primary') : themeColor('border'),
89
+ borderColor: isFocused ? theme.palette.primary[500] : theme.borders.default,
85
90
  },
86
91
  // Header
87
- Box({ borderStyle: 'single', borderColor: isFocused ? themeColor('primary') : themeColor('border'), paddingX: 1 }, Text({ color: themeColor('primary'), bold: true }, 'Stories')),
92
+ Box({ borderStyle: 'single', borderColor: isFocused ? theme.palette.primary[500] : theme.borders.default, paddingX: 1 }, Text({ color: theme.palette.primary[500], bold: true }, 'Stories')),
88
93
  // Categories (horizontal tabs) - clickable
89
- Box({ flexDirection: 'row', paddingX: 1, marginY: 1 }, Text({ color: themeColor('mutedForeground') }, '◀ ▶ '), ...categories.map((cat, catIdx) => {
94
+ Box({ flexDirection: 'row', paddingX: 1, marginY: 1 }, Text({ color: theme.foreground.muted }, '◀ ▶ '), ...categories.map((cat, catIdx) => {
90
95
  const isActive = cat === currentCategory;
91
96
  const count = getStoriesByCategory(cat).length;
92
97
  const shortName = cat.slice(0, 4);
@@ -96,13 +101,13 @@ function Sidebar(props) {
96
101
  onCategoryClick?.(catIdx);
97
102
  },
98
103
  }, Text({
99
- color: isActive ? themeColor('primary') : themeColor('mutedForeground'),
104
+ color: isActive ? theme.palette.primary[500] : theme.foreground.muted,
100
105
  bold: isActive,
101
106
  inverse: isActive,
102
107
  }, isActive ? `[${shortName}:${count}]` : ` ${shortName}:${count} `));
103
- })), Divider({ color: themeColor('border') }),
108
+ })), Divider({ color: theme.borders.default }),
104
109
  // Scroll up indicator (always present, may be empty)
105
- Box({ paddingX: 1, height: 1 }, Text({ color: themeColor('primary'), dim: true }, scrollUpText)),
110
+ Box({ paddingX: 1, height: 1 }, Text({ color: theme.palette.primary[500], dim: true }, scrollUpText)),
106
111
  // Stories list (fixed height container) - clickable items
107
112
  Box({ flexDirection: 'column', paddingX: 1, flexGrow: 1 }, ...visibleStories.map((story, idx) => {
108
113
  const actualIdx = startIdx + idx;
@@ -113,32 +118,33 @@ function Sidebar(props) {
113
118
  props.onStorySelect(actualIdx);
114
119
  },
115
120
  }, Text({
116
- color: isSelected ? themeColor('foreground') : themeColor('mutedForeground'),
121
+ color: isSelected ? theme.foreground.primary : theme.foreground.muted,
117
122
  bold: isSelected,
118
123
  inverse: isSelected && isFocused,
119
124
  }, isSelected ? ` ${story.name} ` : ` ${story.name}`));
120
125
  })),
121
126
  // Scroll down indicator (always present, may be empty)
122
- Box({ paddingX: 1, height: 1 }, Text({ color: themeColor('primary'), dim: true }, scrollDownText)),
127
+ Box({ paddingX: 1, height: 1 }, Text({ color: theme.palette.primary[500], dim: true }, scrollDownText)),
123
128
  // Footer
124
- Box({ borderStyle: 'single', borderColor: themeColor('border'), paddingX: 1 }, Text({ color: themeColor('mutedForeground'), dim: true }, `${selectedIndex + 1}/${stories.length}`)));
129
+ Box({ borderStyle: 'single', borderColor: theme.borders.default, paddingX: 1 }, Text({ color: theme.foreground.muted, dim: true }, `${selectedIndex + 1}/${stories.length}`)));
125
130
  }
126
131
  /**
127
132
  * Control Panel component - edit story props
128
133
  */
129
134
  function ControlPanel(props) {
135
+ const theme = getTheme();
130
136
  const { story, values, focusedControlIndex, focusArea, isEditingText, editingTextValue, onTextChange, onFocus, onSelectControl, onStartTextEdit } = props;
131
137
  const isFocused = focusArea === 'controls';
132
138
  const controls = Object.entries(story.controls || {});
133
139
  if (controls.length === 0) {
134
- return Box({ padding: 1, borderStyle: 'single', borderColor: themeColor('border') }, Text({ color: themeColor('mutedForeground'), dim: true }, 'No controls defined'));
140
+ return Box({ padding: 1, borderStyle: 'single', borderColor: theme.borders.default }, Text({ color: theme.foreground.muted, dim: true }, 'No controls defined'));
135
141
  }
136
142
  return Box({
137
143
  flexDirection: 'column',
138
144
  borderStyle: 'single',
139
- borderColor: isFocused ? themeColor('primary') : themeColor('border'),
145
+ borderColor: isFocused ? theme.palette.primary[500] : theme.borders.default,
140
146
  width: 30,
141
- }, Box({ borderStyle: 'single', borderColor: isFocused ? themeColor('primary') : themeColor('border'), paddingX: 1 }, Text({ color: themeColor('warning'), bold: true }, 'Controls')), Box({ flexDirection: 'column', padding: 1 }, ...controls.map(([key, control], idx) => {
147
+ }, Box({ borderStyle: 'single', borderColor: isFocused ? theme.palette.primary[500] : theme.borders.default, paddingX: 1 }, Text({ color: theme.accents.warning, bold: true }, 'Controls')), Box({ flexDirection: 'column', padding: 1 }, ...controls.map(([key, control], idx) => {
142
148
  const value = values[key] ?? control.defaultValue;
143
149
  const isActive = idx === focusedControlIndex && isFocused;
144
150
  const isThisEditingText = isActive && isEditingText && control.type === 'text';
@@ -158,7 +164,7 @@ function ControlPanel(props) {
158
164
  flexDirection: 'column',
159
165
  marginBottom: 1,
160
166
  onClick: handleClick,
161
- }, Box({}, Text({ color: isActive ? themeColor('primary') : themeColor('mutedForeground') }, `${control.label}: `), isThisEditingText
167
+ }, Box({}, Text({ color: isActive ? theme.palette.primary[500] : theme.foreground.muted }, `${control.label}: `), isThisEditingText
162
168
  ? renderTextInputInline(editingTextValue, isActive, onTextChange)
163
169
  : renderControlValue(value, control.type, isActive)));
164
170
  })));
@@ -167,18 +173,20 @@ function ControlPanel(props) {
167
173
  * Render inline text input for editing
168
174
  */
169
175
  function renderTextInputInline(value, isActive, onChange) {
176
+ const theme = getTheme();
170
177
  // Create inline text input with visible cursor
171
178
  const cursorChar = '▎';
172
- return Box({ flexDirection: 'row' }, Text({ color: themeColor('foreground') }, '"'), Text({ color: themeColor('primary') }, value), isActive ? Text({ color: themeColor('primary'), bold: true }, cursorChar) : null, Text({ color: themeColor('foreground') }, '"'));
179
+ return Box({ flexDirection: 'row' }, Text({ color: theme.foreground.primary }, '"'), Text({ color: theme.palette.primary[500] }, value), isActive ? Text({ color: theme.palette.primary[500], bold: true }, cursorChar) : null, Text({ color: theme.foreground.primary }, '"'));
173
180
  }
174
181
  /**
175
182
  * Render control value display
176
183
  */
177
184
  function renderControlValue(value, type, isActive) {
178
- const color = isActive ? themeColor('foreground') : themeColor('mutedForeground');
185
+ const theme = getTheme();
186
+ const color = isActive ? theme.foreground.primary : theme.foreground.muted;
179
187
  switch (type) {
180
188
  case 'boolean':
181
- return Text({ color: value ? themeColor('success') : themeColor('error') }, value ? 'true' : 'false');
189
+ return Text({ color: value ? theme.accents.positive : theme.accents.critical }, value ? 'true' : 'false');
182
190
  case 'number':
183
191
  case 'range':
184
192
  return Text({ color }, String(value));
@@ -196,50 +204,53 @@ function renderControlValue(value, type, isActive) {
196
204
  * Docs view - show story documentation
197
205
  */
198
206
  function DocsView(props) {
207
+ const theme = getTheme();
199
208
  const { story } = props;
200
209
  const controls = Object.entries(story.controls || {});
201
210
  const children = [
202
211
  // Title
203
- Box({ marginBottom: 1 }, Text({ color: themeColor('primary'), bold: true }, `📖 ${story.name}`)),
212
+ Box({ marginBottom: 1 }, Text({ color: theme.palette.primary[500], bold: true }, `📖 ${story.name}`)),
204
213
  ];
205
214
  // Description (optional)
206
215
  if (story.description) {
207
- children.push(Box({ marginBottom: 1 }, Text({ color: themeColor('foreground') }, story.description)));
216
+ children.push(Box({ marginBottom: 1 }, Text({ color: theme.foreground.primary }, story.description)));
208
217
  }
209
218
  // Controls documentation (if any)
210
219
  if (controls.length > 0) {
211
- children.push(Box({ flexDirection: 'column', marginTop: 1 }, Text({ color: themeColor('warning'), bold: true }, 'Props:'), ...controls.map(([key, control]) => Box({ marginLeft: 1 }, Text({ color: themeColor('primary') }, `• ${key}`), Text({ color: themeColor('mutedForeground') }, ` (${control.type})`), Text({ color: themeColor('mutedForeground'), dim: true }, ` - ${control.label}`)))));
220
+ children.push(Box({ flexDirection: 'column', marginTop: 1 }, Text({ color: theme.accents.warning, bold: true }, 'Props:'), ...controls.map(([key, control]) => Box({ marginLeft: 1 }, Text({ color: theme.palette.primary[500] }, `• ${key}`), Text({ color: theme.foreground.muted }, ` (${control.type})`), Text({ color: theme.foreground.muted, dim: true }, ` - ${control.label}`)))));
212
221
  }
213
222
  // Category
214
- children.push(Box({ marginTop: 1 }, Text({ color: themeColor('mutedForeground') }, 'Category: '), Text({ color: themeColor('accent') }, story.category)));
223
+ children.push(Box({ marginTop: 1 }, Text({ color: theme.foreground.muted }, 'Category: '), Text({ color: theme.accents.highlight }, story.category)));
215
224
  return Box({ flexDirection: 'column', padding: 1 }, ...children);
216
225
  }
217
226
  /**
218
227
  * Compare view - show story variants side by side
219
228
  */
220
229
  function CompareView(props) {
230
+ const theme = getTheme();
221
231
  const { story, values } = props;
222
232
  const controls = Object.entries(story.controls || {});
223
233
  // Find a control to compare (prefer select or boolean)
224
234
  const compareControl = controls.find(([, c]) => c.type === 'select' || c.type === 'boolean');
225
235
  if (!compareControl) {
226
- return Box({ padding: 1 }, Text({ color: themeColor('mutedForeground') }, 'No comparable props found for this story'));
236
+ return Box({ padding: 1 }, Text({ color: theme.foreground.muted }, 'No comparable props found for this story'));
227
237
  }
228
238
  const [key, control] = compareControl;
229
239
  const variants = control.type === 'select' ? (control.options || []) :
230
240
  control.type === 'boolean' ? [true, false] : [];
231
- return Box({ flexDirection: 'column', padding: 1 }, Text({ color: themeColor('success'), bold: true }, `🔀 Comparing: ${control.label}`), Divider({ color: themeColor('border') }), Box({ flexDirection: 'row', flexWrap: 'wrap', gap: 1 }, ...variants.slice(0, 4).map((variant) => Box({
241
+ return Box({ flexDirection: 'column', padding: 1 }, Text({ color: theme.accents.positive, bold: true }, `🔀 Comparing: ${control.label}`), Divider({ color: theme.borders.default }), Box({ flexDirection: 'row', flexWrap: 'wrap', gap: 1 }, ...variants.slice(0, 4).map((variant) => Box({
232
242
  flexDirection: 'column',
233
243
  borderStyle: 'single',
234
- borderColor: values[key] === variant ? themeColor('primary') : themeColor('border'),
244
+ borderColor: values[key] === variant ? theme.palette.primary[500] : theme.borders.default,
235
245
  padding: 1,
236
246
  minWidth: 20,
237
- }, Text({ color: themeColor('primary'), dim: true }, String(variant)), Box({ marginTop: 1 }, StoryContent({ story, values: { ...values, [key]: variant }, frame: 0 }))))));
247
+ }, Text({ color: theme.palette.primary[500], dim: true }, String(variant)), Box({ marginTop: 1 }, StoryContent({ story, values: { ...values, [key]: variant }, frame: 0 }))))));
238
248
  }
239
249
  /**
240
250
  * Preview Panel component - render story with mode-specific content
241
251
  */
242
252
  function PreviewPanel(props) {
253
+ const theme = getTheme();
243
254
  const { story, values, viewMode, focusArea, frame, isPaused } = props;
244
255
  const isFocused = focusArea === 'preview';
245
256
  const isAnimated = story.animation?.enabled;
@@ -260,51 +271,49 @@ function PreviewPanel(props) {
260
271
  const previewChildren = [];
261
272
  // Header with optional animation indicator
262
273
  const headerChildren = [
263
- Text({ color: themeColor('mutedForeground') }, `${story.category} / `),
264
- Text({ color: themeColor('primary'), bold: true }, story.name),
274
+ Text({ color: theme.foreground.muted }, `${story.category} / `),
275
+ Text({ color: theme.palette.primary[500], bold: true }, story.name),
265
276
  ];
266
277
  if (isAnimated) {
267
- headerChildren.push(Text({ color: isPaused ? themeColor('warning') : themeColor('success') }, isPaused ? ' ⏸' : ' ▶'));
278
+ headerChildren.push(Text({ color: isPaused ? theme.accents.warning : theme.accents.positive }, isPaused ? ' ⏸' : ' ▶'));
268
279
  }
269
- previewChildren.push(Box({ borderStyle: 'single', borderColor: isFocused ? themeColor('primary') : themeColor('border'), paddingX: 1 }, ...headerChildren));
280
+ previewChildren.push(Box({ borderStyle: 'single', borderColor: isFocused ? theme.palette.primary[500] : theme.borders.default, paddingX: 1 }, ...headerChildren));
270
281
  // View mode tabs
271
282
  const modeText = isAnimated
272
283
  ? `[P][G][C][D] ${viewMode.toUpperCase()} Frame:${frame}`
273
284
  : `[P][G][C][D] ${viewMode.toUpperCase()}`;
274
285
  previewChildren.push(Box({ paddingX: 1 }, Text({ color: getModeColor(viewMode) }, modeText)));
275
286
  if ((viewMode === 'preview' || viewMode === 'playground') && story.description) {
276
- previewChildren.push(Box({ paddingX: 1 }, Text({ color: themeColor('mutedForeground'), dim: true }, story.description)));
287
+ previewChildren.push(Box({ paddingX: 1 }, Text({ color: theme.foreground.muted, dim: true }, story.description)));
277
288
  }
278
- previewChildren.push(Divider({ color: themeColor('border') }));
289
+ previewChildren.push(Divider({ color: theme.borders.default }));
279
290
  previewChildren.push(renderContent());
280
291
  return Box({
281
292
  flexDirection: 'column',
282
293
  flexGrow: 1,
283
294
  borderStyle: 'single',
284
- borderColor: isFocused ? themeColor('primary') : themeColor('border'),
295
+ borderColor: isFocused ? theme.palette.primary[500] : theme.borders.default,
285
296
  }, ...previewChildren);
286
297
  }
287
298
  /**
288
299
  * Get color for view mode
289
300
  */
290
301
  function getModeColor(mode) {
302
+ const theme = getTheme();
291
303
  switch (mode) {
292
- case 'preview': return themeColor('primary');
293
- case 'playground': return themeColor('warning');
294
- case 'comparatives': return themeColor('success');
295
- case 'docs': return themeColor('accent');
296
- default: return themeColor('foreground');
304
+ case 'preview': return theme.palette.primary[500];
305
+ case 'playground': return theme.accents.warning;
306
+ case 'comparatives': return theme.accents.positive;
307
+ case 'docs': return theme.accents.highlight;
308
+ default: return theme.foreground.primary;
297
309
  }
298
310
  }
299
311
  /**
300
- * Status bar component
312
+ * Text editing mode indicator
301
313
  */
302
- function StatusBar(props) {
303
- const { isEditingText } = props;
304
- if (isEditingText) {
305
- return Box({ borderStyle: 'single', borderColor: themeColor('warning'), paddingX: 1 }, Text({ color: themeColor('warning'), bold: true }, '✏️ EDITING TEXT '), Text({ color: themeColor('mutedForeground') }, '[Enter] Save '), Text({ color: themeColor('mutedForeground') }, '[Esc] Cancel '), Text({ color: themeColor('mutedForeground') }, '[Backspace] Delete'));
306
- }
307
- return Box({ borderStyle: 'single', borderColor: themeColor('border'), paddingX: 1 }, Text({ color: themeColor('mutedForeground') }, '[Esc] Back/Quit '), Text({ color: themeColor('mutedForeground') }, '[Enter] Select '), Text({ color: themeColor('mutedForeground') }, '[Tab] Focus '), Text({ color: themeColor('mutedForeground') }, '[F1] Theme '), Text({ color: themeColor('mutedForeground') }, '[F12] Logs '), Text({ color: themeColor('primary'), dim: true }, '🖱️ Mouse enabled'));
314
+ function TextEditingIndicator() {
315
+ const theme = getTheme();
316
+ return Box({ borderStyle: 'single', borderColor: theme.accents.warning, paddingX: 1 }, Text({ color: theme.accents.warning, bold: true }, '✏️ EDITING TEXT '), Text({ color: theme.foreground.muted }, '[Enter] Save '), Text({ color: theme.foreground.muted }, '[Esc] Cancel '), Text({ color: theme.foreground.muted }, '[Backspace] Delete'));
308
317
  }
309
318
  /**
310
319
  * Format elapsed time
@@ -314,55 +323,52 @@ function formatTime(seconds) {
314
323
  const secs = seconds % 60;
315
324
  return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
316
325
  }
317
- /**
318
- * Navbar component with metrics
319
- */
320
- function Navbar(props) {
321
- const { componentCount, clicks, keystrokes, fps, elapsedSeconds, themeName } = props;
322
- const fpsColor = fps >= 30 ? themeColor('success') : fps >= 15 ? themeColor('warning') : themeColor('error');
323
- return Box({
324
- flexDirection: 'row',
325
- justifyContent: 'space-between',
326
- borderStyle: 'single',
327
- borderColor: themeColor('accent'),
328
- }, Box({ flexDirection: 'row' }, Text({ color: themeColor('accent'), bold: true }, ' Tuiuiu.js '), Text({ color: themeColor('warning'), dim: true }, `[${themeName}] `)), Box({ flexDirection: 'row' }, Text({ color: themeColor('foreground'), bold: true }, formatTime(elapsedSeconds)), Text({ color: themeColor('mutedForeground'), dim: true }, ' '), Text({ color: themeColor('primary') }, String(componentCount)), Text({ color: themeColor('mutedForeground'), dim: true }, 's '), Text({ color: themeColor('warning') }, String(clicks)), Text({ color: themeColor('mutedForeground'), dim: true }, 'c '), Text({ color: themeColor('success') }, String(keystrokes)), Text({ color: themeColor('mutedForeground'), dim: true }, 'k '), Text({ color: fpsColor, bold: true }, `${fps}fps `)));
329
- }
330
326
  // =============================================================================
331
327
  // Splash Screen
332
328
  // =============================================================================
333
329
  // Module-level splash state (created once when storybook starts)
334
330
  let splashState = null;
331
+ // Module-level flag for splash completion
332
+ let splashCompleted = false;
333
+ let splashCompletionCallbacks = [];
335
334
  function getSplashState() {
336
335
  if (!splashState) {
337
336
  splashState = createSplashScreen({
338
337
  duration: 1500,
339
338
  fadeInDuration: 300,
339
+ onComplete: () => {
340
+ splashCompleted = true;
341
+ // Notify all callbacks
342
+ for (const cb of splashCompletionCallbacks) {
343
+ cb();
344
+ }
345
+ splashCompletionCallbacks = [];
346
+ },
340
347
  });
341
348
  }
342
349
  return splashState;
343
350
  }
351
+ function onSplashComplete(callback) {
352
+ if (splashCompleted) {
353
+ callback();
354
+ }
355
+ else {
356
+ splashCompletionCallbacks.push(callback);
357
+ }
358
+ }
344
359
  /**
345
360
  * Main Storybook App
346
361
  */
347
362
  function StorybookApp() {
363
+ const theme = getTheme();
348
364
  const app = useApp();
349
365
  // Get or create splash state
350
366
  const splash = getSplashState();
351
- // Render splash screen if visible
352
- if (splash.isVisible()) {
353
- const splashNode = SplashScreen({
354
- coloredArt: TUIUIU_BIRD_COLORED,
355
- subtitle: 'Component Explorer',
356
- version: '1.0.0',
357
- loadingType: 'spinner',
358
- spinnerStyle: 'dots',
359
- loadingMessage: 'Loading stories...',
360
- state: splash,
361
- });
362
- // Return splash or empty box (SplashScreen can return null when not visible)
363
- return splashNode ?? Box({});
364
- }
365
- // State
367
+ // =========================================================================
368
+ // ALL HOOKS MUST BE CALLED BEFORE ANY CONDITIONAL RETURNS
369
+ // This ensures consistent hook ordering across renders
370
+ // =========================================================================
371
+ // State - always call these regardless of splash visibility
366
372
  const categories = getCategories();
367
373
  const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0);
368
374
  const [selectedStoryIndex, setSelectedStoryIndex] = useState(0);
@@ -375,6 +381,8 @@ function StorybookApp() {
375
381
  const [editingTextValue, setEditingTextValue] = useState('');
376
382
  // Animation state - now uses global tick
377
383
  const [isPaused, setIsPaused] = useState(false);
384
+ // Track if we've already cleared screen after splash
385
+ const [splashDone, setSplashDone] = useState(false);
378
386
  // Get animation frame from global tick
379
387
  const animationFrame = () => getTick();
380
388
  // Metrics state
@@ -450,11 +458,27 @@ function StorybookApp() {
450
458
  }));
451
459
  };
452
460
  const controlKeys = currentStory ? Object.keys(currentStory.controls || {}) : [];
461
+ // Search state from store
462
+ const [searchSelectedIndex, setSearchSelectedIndex] = useState(0);
463
+ // Get search results
464
+ const storeState = storybookStore.state();
465
+ const searchResults = storeState.searchVisible
466
+ ? searchStories(allStories, storeState.searchQuery)
467
+ : [];
453
468
  // Input handling
454
469
  useInput((input, key) => {
455
470
  setKeystrokeCount((c) => c + 1);
456
- // Toggle Theme with F1
471
+ // Record key press for indicator
472
+ recordKeyPress(input, key);
473
+ clearOldKeyPresses();
474
+ // Toggle Search with F1
457
475
  if (key.f1) {
476
+ storybookStore.dispatch({ type: 'TOGGLE_SEARCH' });
477
+ setSearchSelectedIndex(0);
478
+ return;
479
+ }
480
+ // Toggle Theme with F2
481
+ if (key.f2) {
458
482
  const currentTheme = useTheme();
459
483
  const nextTheme = getNextTheme(currentTheme);
460
484
  setTheme(nextTheme);
@@ -470,6 +494,56 @@ function StorybookApp() {
470
494
  storybookStore.dispatch({ type: 'CLEAR_LOGS' });
471
495
  }
472
496
  // =========================================================================
497
+ // SEARCH MODE
498
+ // =========================================================================
499
+ if (storeState.searchVisible) {
500
+ // Escape - close search
501
+ if (key.escape) {
502
+ storybookStore.dispatch({ type: 'CLOSE_SEARCH' });
503
+ return;
504
+ }
505
+ // Navigate results
506
+ if (key.upArrow) {
507
+ setSearchSelectedIndex((i) => Math.max(0, i - 1));
508
+ return;
509
+ }
510
+ if (key.downArrow) {
511
+ setSearchSelectedIndex((i) => Math.min(searchResults.length - 1, i + 1));
512
+ return;
513
+ }
514
+ // Select result and navigate
515
+ if (key.return && searchResults.length > 0) {
516
+ const selected = searchResults[searchSelectedIndex()];
517
+ if (selected) {
518
+ // Find category and story indices
519
+ const catIndex = categories.indexOf(selected.story.category);
520
+ const catStories = getStoriesByCategory(selected.story.category);
521
+ const storyIndex = catStories.findIndex((s) => s.name === selected.story.name);
522
+ if (catIndex >= 0)
523
+ setCurrentCategoryIndex(catIndex);
524
+ if (storyIndex >= 0)
525
+ setSelectedStoryIndex(storyIndex);
526
+ storybookStore.dispatch({ type: 'CLOSE_SEARCH' });
527
+ }
528
+ return;
529
+ }
530
+ // Backspace - delete character from query
531
+ if (key.backspace) {
532
+ const current = storeState.searchQuery;
533
+ storybookStore.dispatch({ type: 'SET_SEARCH_QUERY', payload: current.slice(0, -1) });
534
+ setSearchSelectedIndex(0);
535
+ return;
536
+ }
537
+ // Regular character input - append to search query
538
+ if (input && input.length > 0 && !key.ctrl && !key.meta) {
539
+ storybookStore.dispatch({ type: 'SET_SEARCH_QUERY', payload: storeState.searchQuery + input });
540
+ setSearchSelectedIndex(0);
541
+ return;
542
+ }
543
+ // Consume all other keys while searching
544
+ return;
545
+ }
546
+ // =========================================================================
473
547
  // TEXT EDITING MODE
474
548
  // =========================================================================
475
549
  if (isEditingText()) {
@@ -641,17 +715,66 @@ function StorybookApp() {
641
715
  }
642
716
  }
643
717
  });
718
+ // =========================================================================
719
+ // SPLASH→MAIN TRANSITION
720
+ // Use callback-based approach to ensure state update triggers re-render
721
+ // =========================================================================
722
+ // Register callback to update state when splash completes
723
+ useEffect(() => {
724
+ if (!splashDone()) {
725
+ onSplashComplete(() => {
726
+ app.clearScreen?.();
727
+ setSplashDone(true);
728
+ });
729
+ }
730
+ });
731
+ // Render splash while not done (use module-level flag + state for reactivity)
732
+ if (!splashDone() && !splashCompleted) {
733
+ const splashNode = SplashScreen({
734
+ coloredArt: TUIUIU_BIRD_COLORED,
735
+ subtitle: 'Component Explorer',
736
+ version: getVersionSync(),
737
+ loadingType: 'spinner',
738
+ spinnerStyle: 'dots',
739
+ loadingMessage: 'Loading stories...',
740
+ state: splash,
741
+ });
742
+ return splashNode ?? Box({});
743
+ }
744
+ // Wait for state to update
745
+ if (!splashDone()) {
746
+ return Box({});
747
+ }
644
748
  if (!currentStory) {
645
- return Box({ flexDirection: 'column', padding: 2 }, Text({ color: themeColor('warning') }, 'No stories found'), Text({ color: themeColor('mutedForeground') }, 'Add stories to src/storybook/stories/'));
749
+ return Box({ flexDirection: 'column', padding: 2 }, Text({ color: theme.accents.warning }, 'No stories found'), Text({ color: theme.foreground.muted }, 'Add stories to src/storybook/stories/'));
646
750
  }
647
- return Box({ flexDirection: 'column', height: '100%' }, Navbar({
648
- componentCount: allStories.length,
751
+ return Box({ flexDirection: 'column', height: '100%' },
752
+ // =========================================================================
753
+ // HEADER SECTION
754
+ // =========================================================================
755
+ // Enhanced Navbar with branding
756
+ Navbar({ componentCount: allStories.length }),
757
+ // StatusBar with metrics
758
+ MetricsStatusBar({
759
+ themeName: useTheme().name,
649
760
  clicks: clickCount(),
650
761
  keystrokes: keystrokeCount(),
651
762
  fps: fps(),
652
763
  elapsedSeconds: elapsedSeconds(),
653
- themeName: useTheme().name,
654
- }), Box({ flexDirection: 'row', flexGrow: 1 }, Sidebar({
764
+ }),
765
+ // Search bar (conditional)
766
+ SearchBar({
767
+ query: storeState.searchQuery,
768
+ results: searchResults,
769
+ selectedIndex: searchSelectedIndex(),
770
+ isVisible: storeState.searchVisible,
771
+ }),
772
+ // Text editing indicator (when editing)
773
+ isEditingText() ? TextEditingIndicator() : null,
774
+ // =========================================================================
775
+ // MAIN CONTENT
776
+ // =========================================================================
777
+ Box({ flexDirection: 'row', flexGrow: 1 }, Sidebar({
655
778
  categories,
656
779
  currentCategory,
657
780
  stories,
@@ -687,17 +810,28 @@ function StorybookApp() {
687
810
  setEditingTextValue(value);
688
811
  setIsEditingText(true);
689
812
  },
690
- })), StatusBar({ viewMode: viewMode(), focusArea: focusArea(), isEditingText: isEditingText() }),
691
- // LOG VIEWER OVERLAY
692
- LogViewer());
813
+ })),
814
+ // =========================================================================
815
+ // FOOTER SECTION (Console > Hotkeys > Pressed Keys)
816
+ // =========================================================================
817
+ // Console accordion
818
+ ConsoleAccordion(),
819
+ // Hotkeys panel
820
+ HotkeysPanel(),
821
+ // Pressed keys indicator
822
+ PressedKeysIndicator());
693
823
  }
694
824
  /**
695
825
  * Run the storybook
696
826
  */
697
827
  export async function runStorybook() {
828
+ // Pre-cache version (async load, then sync access everywhere)
829
+ await getVersion();
698
830
  // Start global tick for animations
699
831
  startTick(100);
700
- const { waitUntilExit } = render(() => StorybookApp());
832
+ const { waitUntilExit } = render(() => StorybookApp(), {
833
+ fullHeight: true, // Fill entire terminal to cover splash screen content
834
+ });
701
835
  await waitUntilExit();
702
836
  // Cleanup tick on exit
703
837
  stopTick();