@titan-design/react-ui 0.12.1 → 0.14.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 (531) hide show
  1. package/README.md +60 -47
  2. package/dist/bodymap.js +856 -736
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +858 -738
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1431 -237
  7. package/dist/index.d.ts +1431 -237
  8. package/dist/index.js +6506 -4781
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +6280 -4642
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
  13. package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1919 -1937
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1927 -1945
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +225 -140
  21. package/dist/theme/index.d.ts +225 -140
  22. package/dist/theme/index.js +298 -319
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +283 -315
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +145 -24
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +145 -24
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +128 -11
  31. package/dist/theme/tokens.d.ts +128 -11
  32. package/dist/theme/tokens.js +126 -26
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +124 -27
  35. package/dist/theme/tokens.mjs.map +1 -1
  36. package/docs/audits/2026-09-08-critique-addenda.md +51 -0
  37. package/docs/audits/2026-09-08-library-critique.md +212 -0
  38. package/docs/library-roadmap.md +99 -0
  39. package/docs/render-testing-pattern.md +130 -0
  40. package/package.json +1 -1
  41. package/src/arch/Architecture.stories.tsx +3 -1
  42. package/src/arch/arch-graph.freshness.test.ts +52 -0
  43. package/src/arch/arch-graph.json +4989 -1583
  44. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  45. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  46. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  48. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  49. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  51. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  52. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  54. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  55. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  57. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  58. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  60. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  61. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  63. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  64. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +182 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  66. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  67. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  69. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  70. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  71. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  73. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  74. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  76. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  77. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  78. package/src/components/custom/ActiveWork/README.md +305 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  80. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  81. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  82. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  83. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  84. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  86. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  87. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  88. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  90. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  91. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  92. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  93. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  94. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  95. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  96. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  97. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  98. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  99. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  100. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  101. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  102. package/src/components/custom/ActiveWork/index.ts +19 -0
  103. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  104. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  105. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  106. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  107. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +12 -6
  108. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  109. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  110. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  111. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  112. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  113. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  116. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  117. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  119. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  120. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  121. package/src/components/custom/Fatigue/README.md +40 -3
  122. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  123. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  124. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  125. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  126. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  127. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  128. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  129. package/src/components/custom/Fatigue/index.ts +17 -5
  130. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  131. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  132. package/src/components/custom/Gauge/Gauge.stories.tsx +6 -3
  133. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  134. package/src/components/custom/Gauge/Gauge.tsx +7 -2
  135. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  136. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  137. package/src/components/custom/Metric/Metric.tsx +5 -20
  138. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  139. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  140. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  141. package/src/components/custom/Prose/README.md +58 -0
  142. package/src/components/custom/Prose/index.ts +1 -0
  143. package/src/components/custom/Scatter/Scatter.stories.tsx +55 -14
  144. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  145. package/src/components/custom/Scatter/Scatter.tsx +122 -18
  146. package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
  147. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  148. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  149. package/src/components/custom/Table/Table.stories.tsx +99 -16
  150. package/src/components/custom/Table/Table.test.tsx +273 -86
  151. package/src/components/custom/Table/Table.tsx +159 -67
  152. package/src/components/custom/Table/column-fit.test.ts +75 -0
  153. package/src/components/custom/Table/column-fit.ts +116 -0
  154. package/src/components/custom/Table/index.ts +9 -0
  155. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  156. package/src/components/custom/Treemap/Treemap.stories.tsx +15 -12
  157. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  158. package/src/components/custom/Treemap/Treemap.tsx +11 -3
  159. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  160. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  161. package/src/components/custom/Typography/Typography.tsx +6 -0
  162. package/src/components/custom/Typography/index.ts +1 -8
  163. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +11 -2
  164. package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
  165. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  166. package/src/components/custom/Workout/BaseBadge.tsx +6 -2
  167. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  168. package/src/components/custom/Workout/BodyMap.tsx +8 -4
  169. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
  170. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  171. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
  172. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
  173. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  174. package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
  175. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  176. package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
  177. package/src/components/custom/Workout/DeviationBar.tsx +24 -16
  178. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
  179. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
  180. package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
  181. package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
  182. package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
  183. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
  184. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
  185. package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
  186. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  187. package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
  188. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  189. package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
  190. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
  191. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  192. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  193. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  194. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  195. package/src/components/custom/Workout/InputBar.tsx +4 -2
  196. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  197. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  198. package/src/components/custom/Workout/IntensityBar.tsx +8 -3
  199. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  200. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  201. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  202. package/src/components/custom/Workout/MesoCard.tsx +14 -10
  203. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  204. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  205. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  206. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  207. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  208. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -33
  209. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  210. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  211. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  212. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  213. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +9 -4
  214. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  215. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  216. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  217. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  218. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  219. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +16 -3
  220. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  221. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  222. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  223. package/src/components/custom/Workout/README.md +68 -17
  224. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  225. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  226. package/src/components/custom/Workout/ReadinessCheck.tsx +22 -4
  227. package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
  228. package/src/components/custom/Workout/RestTimer.tsx +4 -2
  229. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  230. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  231. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  232. package/src/components/custom/Workout/SegmentedBar.stories.tsx +21 -17
  233. package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
  234. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  235. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  236. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  237. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  238. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  239. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  240. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  241. package/src/components/custom/Workout/SetBar.tsx +38 -12
  242. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  243. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  244. package/src/components/custom/Workout/SetRow.tsx +61 -23
  245. package/src/components/custom/Workout/SetStrip.stories.tsx +63 -7
  246. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  247. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  248. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  249. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  250. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  251. package/src/components/custom/Workout/SetsRepsLoad.tsx +24 -12
  252. package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
  253. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  254. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  255. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  256. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  257. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  258. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  259. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  260. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  261. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +11 -4
  262. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  263. package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
  264. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  265. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  266. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  267. package/src/components/custom/Workout/TempoDisplay.stories.tsx +26 -10
  268. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  269. package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
  270. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  271. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  272. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +10 -6
  273. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
  274. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
  275. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  276. package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
  277. package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
  278. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  279. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  280. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  281. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  282. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  283. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  284. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  285. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  286. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  287. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  288. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  289. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  290. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  291. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  292. package/src/components/custom/Workout/ZoneTrack.stories.tsx +10 -7
  293. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  294. package/src/components/custom/Workout/exerciseRowState.ts +29 -0
  295. package/src/components/custom/Workout/index.ts +17 -2
  296. package/src/components/custom/Workout/metricText.stories.tsx +15 -13
  297. package/src/components/custom/Workout/metricText.tsx +8 -2
  298. package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
  299. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  300. package/src/components/custom/charts/README.md +66 -0
  301. package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
  302. package/src/components/custom/charts/SetBarChart.tsx +56 -9
  303. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  304. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  305. package/src/components/custom/charts/SparkBars.tsx +85 -0
  306. package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
  307. package/src/components/custom/charts/flatBarGeometry.ts +32 -0
  308. package/src/components/custom/charts/index.ts +3 -0
  309. package/src/components/custom/index.ts +3 -0
  310. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  311. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  312. package/src/components/custom/stepper/Stepper.tsx +21 -34
  313. package/src/components/icons/icons.stories.tsx +1 -1
  314. package/src/components/icons/icons.tsx +51 -0
  315. package/src/components/shell/AppShell.stories.tsx +150 -0
  316. package/src/components/shell/AppShell.test.tsx +70 -0
  317. package/src/components/shell/AppShell.tsx +90 -0
  318. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  319. package/src/components/shell/BrandLockup.test.tsx +34 -1
  320. package/src/components/shell/BrandLockup.tsx +27 -11
  321. package/src/components/shell/NavItem.stories.tsx +4 -4
  322. package/src/components/shell/NavItem.test.tsx +17 -0
  323. package/src/components/shell/NavItem.tsx +17 -5
  324. package/src/components/shell/README.md +89 -26
  325. package/src/components/shell/SideNav.stories.tsx +38 -7
  326. package/src/components/shell/SideNav.test.tsx +16 -8
  327. package/src/components/shell/SideNav.tsx +17 -17
  328. package/src/components/shell/TopBar.stories.tsx +44 -19
  329. package/src/components/shell/TopBar.test.tsx +34 -12
  330. package/src/components/shell/TopBar.tsx +61 -40
  331. package/src/components/shell/brands.tsx +66 -0
  332. package/src/components/shell/index.ts +9 -8
  333. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  334. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  335. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  336. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  337. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  338. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  339. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  340. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  341. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  342. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  343. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  344. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  345. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  346. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  347. package/src/components/shell/workout/index.ts +9 -0
  348. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  349. package/src/components/ui/README.md +110 -0
  350. package/src/components/ui/alert/Alert.stories.tsx +15 -5
  351. package/src/components/ui/alert/Alert.tsx +24 -3
  352. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  353. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  354. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  355. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  356. package/src/components/ui/avatar/Avatar.tsx +2 -10
  357. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  358. package/src/components/ui/badge/Badge.test.tsx +19 -3
  359. package/src/components/ui/badge/Badge.tsx +48 -55
  360. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  361. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  362. package/src/components/ui/button/Button.stories.tsx +46 -12
  363. package/src/components/ui/button/Button.tsx +44 -37
  364. package/src/components/ui/card/Card.stories.tsx +90 -94
  365. package/src/components/ui/card/Card.test.tsx +80 -6
  366. package/src/components/ui/card/Card.tsx +150 -203
  367. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  368. package/src/components/ui/checkbox/Checkbox.tsx +7 -27
  369. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  370. package/src/components/ui/chip/Chip.test.tsx +10 -6
  371. package/src/components/ui/chip/Chip.tsx +76 -91
  372. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  373. package/src/components/ui/collapse/Collapse.tsx +6 -11
  374. package/src/components/ui/data-row/DataRow.stories.tsx +14 -3
  375. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  376. package/src/components/ui/data-row/DataRow.tsx +18 -7
  377. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  378. package/src/components/ui/divider/Divider.tsx +1 -5
  379. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  380. package/src/components/ui/drawer/Drawer.tsx +13 -27
  381. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  382. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  383. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  384. package/src/components/ui/form-field/FormField.tsx +10 -46
  385. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  386. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  387. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  388. package/src/components/ui/help-tip/index.ts +7 -1
  389. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  390. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  391. package/src/components/ui/icon-box/IconBox.tsx +10 -18
  392. package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
  393. package/src/components/ui/indicator/Indicator.tsx +20 -14
  394. package/src/components/ui/input/Input.stories.tsx +3 -11
  395. package/src/components/ui/input/Input.test.tsx +2 -10
  396. package/src/components/ui/input/Input.tsx +10 -25
  397. package/src/components/ui/input/index.ts +7 -1
  398. package/src/components/ui/link/Link.stories.tsx +19 -7
  399. package/src/components/ui/list-item/ListItem.stories.tsx +17 -5
  400. package/src/components/ui/list-item/ListItem.tsx +2 -9
  401. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  402. package/src/components/ui/menu/Menu.test.tsx +2 -11
  403. package/src/components/ui/menu/Menu.tsx +12 -14
  404. package/src/components/ui/menu/index.ts +8 -1
  405. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  406. package/src/components/ui/modal/Modal.tsx +31 -19
  407. package/src/components/ui/pill/Pill.stories.tsx +111 -23
  408. package/src/components/ui/pill/Pill.test.tsx +101 -3
  409. package/src/components/ui/pill/Pill.tsx +168 -49
  410. package/src/components/ui/pill/index.ts +1 -1
  411. package/src/components/ui/popover/Popover.stories.tsx +6 -8
  412. package/src/components/ui/popover/Popover.test.tsx +1 -6
  413. package/src/components/ui/popover/Popover.tsx +20 -15
  414. package/src/components/ui/popover/index.ts +7 -1
  415. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  416. package/src/components/ui/progress/Progress.test.tsx +1 -5
  417. package/src/components/ui/progress/Progress.tsx +9 -6
  418. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  419. package/src/components/ui/radio/Radio.test.tsx +6 -2
  420. package/src/components/ui/radio/Radio.tsx +23 -22
  421. package/src/components/ui/section/Section.stories.tsx +12 -21
  422. package/src/components/ui/section/Section.test.tsx +1 -6
  423. package/src/components/ui/section/Section.tsx +1 -3
  424. package/src/components/ui/section/index.ts +1 -5
  425. package/src/components/ui/select/Select.stories.tsx +3 -13
  426. package/src/components/ui/select/Select.test.tsx +2 -11
  427. package/src/components/ui/select/Select.tsx +16 -15
  428. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  429. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  430. package/src/components/ui/skeleton/index.ts +1 -7
  431. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  432. package/src/components/ui/spinner/Spinner.tsx +4 -5
  433. package/src/components/ui/stack/Stack.stories.tsx +8 -8
  434. package/src/components/ui/stack/Stack.tsx +1 -1
  435. package/src/components/ui/surface/Surface.stories.tsx +92 -33
  436. package/src/components/ui/surface/Surface.test.tsx +88 -28
  437. package/src/components/ui/surface/Surface.tsx +37 -87
  438. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  439. package/src/components/ui/surface/index.ts +8 -0
  440. package/src/components/ui/surface/resolveSurface.ts +98 -0
  441. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  442. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  443. package/src/components/ui/switch/Switch.tsx +7 -6
  444. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  445. package/src/components/ui/tabs/Tabs.tsx +8 -21
  446. package/src/components/ui/tabs/index.ts +1 -7
  447. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  448. package/src/components/ui/tile/Tile.tsx +3 -0
  449. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  450. package/src/components/ui/toast/Toast.test.tsx +2 -8
  451. package/src/components/ui/toast/Toast.tsx +49 -57
  452. package/src/components/ui/toast/index.ts +0 -1
  453. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  454. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  455. package/src/components/ui/toolbar-button/ToolbarButton.tsx +38 -49
  456. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  457. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  458. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  459. package/src/components/ui/tooltip/index.ts +1 -1
  460. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  461. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  462. package/src/components/ui/trigger/index.ts +2 -0
  463. package/src/stories/PresetShowcase.stories.tsx +101 -48
  464. package/src/test/no-device-internals.test.ts +3 -3
  465. package/src/test/no-raw-color.test.ts +39 -0
  466. package/src/theme/Alpha.stories.tsx +103 -0
  467. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  468. package/src/theme/ColorPalettes.stories.tsx +62 -45
  469. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  470. package/src/theme/Depth.stories.tsx +132 -65
  471. package/src/theme/DepthCalibration.stories.tsx +23 -21
  472. package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
  473. package/src/theme/ThemeProvider.test.tsx +4 -4
  474. package/src/theme/barrel.ts +7 -6
  475. package/src/theme/color-stories.coverage.test.ts +1 -1
  476. package/src/theme/color-utils.ts +41 -29
  477. package/src/theme/config.completeness.test.ts +1 -1
  478. package/src/theme/config.ts +40 -0
  479. package/src/theme/elevation.test.ts +82 -56
  480. package/src/theme/elevation.ts +77 -561
  481. package/src/theme/global.css +45 -5
  482. package/src/theme/grey-ramp.test.ts +24 -8
  483. package/src/theme/lift.test.ts +39 -0
  484. package/src/theme/lift.ts +102 -0
  485. package/src/theme/materials.test.ts +5 -2
  486. package/src/theme/materials.ts +38 -4
  487. package/src/theme/native-theming-guard.test.ts +5 -5
  488. package/src/theme/presets/apply.ts +4 -0
  489. package/src/theme/presets/audiobook.ts +5 -2
  490. package/src/theme/presets/default.ts +4 -0
  491. package/src/theme/presets/index.ts +14 -1
  492. package/src/theme/presets/types.ts +1 -1
  493. package/src/theme/semantic-contrast.test.ts +13 -4
  494. package/src/theme/status-distinctness.test.ts +66 -0
  495. package/src/theme/subtle-tone-weight.test.ts +163 -0
  496. package/src/theme/surface-planes.test.ts +37 -0
  497. package/src/theme/surface-planes.ts +68 -0
  498. package/src/theme/tailwind-var-opacity.test.ts +163 -0
  499. package/src/theme/token-export-guard.test.ts +1 -3
  500. package/src/theme/tokens/primitives.test.ts +15 -7
  501. package/src/theme/tokens/primitives.ts +98 -33
  502. package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
  503. package/src/theme/tokens/semantic.ts +124 -54
  504. package/src/theme/wash-ladder.test.ts +58 -0
  505. package/src/theme/workout-tokens.ts +1 -7
  506. package/src/types/jest-axe.d.ts +2 -6
  507. package/src/types/raw-md.d.ts +5 -0
  508. package/src/utils/colors.alpha-adoption.test.ts +408 -0
  509. package/src/utils/colors.ts +4 -4
  510. package/src/utils/form.ts +1 -3
  511. package/src/utils/index.ts +2 -7
  512. package/src/utils/number-format.ts +17 -0
  513. package/src/utils/workout-format.ts +15 -6
  514. package/src/web-jsx/jsx-runtime.ts +1 -5
  515. package/tailwind.config.js +55 -9
  516. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  517. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  518. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  519. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  520. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  521. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  522. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  523. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  524. package/src/components/shell/DashboardShell.tsx +0 -75
  525. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  526. package/src/theme/elevation.stories.tsx +0 -346
  527. package/src/utils/useTheme.ts +0 -72
  528. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  529. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  530. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  531. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -1,28 +1,34 @@
1
- import { useState } from 'react'
1
+ import { Fragment, useState, type ReactNode } from 'react'
2
2
  import { View, type LayoutChangeEvent } from 'react-native'
3
3
  import { cn } from '../../utils/cn'
4
4
  import { surfaceGradient } from '../../theme/gradients'
5
5
  import { Divider } from '../ui/divider'
6
6
  import { DateTime } from '../custom/DateTime'
7
7
  import { BrandLockup } from './BrandLockup'
8
- import { SessionStatePill, type SessionState } from './SessionStatePill'
9
- import { DeviceMenu } from './DeviceMenu'
10
- import { type Device } from './DeviceRow'
8
+ import { type BrandKey } from './brands'
11
9
 
12
10
  export interface TopBarProps {
13
- /** Global session state the status pill. */
14
- state: SessionState
15
- /** Devices for the connection glyph + dropdown. */
16
- devices: Device[]
17
- /** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
18
- time?: number | Date
19
- /** Brand subtitle. Default "wall dashboard". */
11
+ /** Which app identity the default {@link BrandLockup} renders. */
12
+ brand?: BrandKey
13
+ /** Brand subtitle. Defaults to the brand preset's own. */
20
14
  subtitle?: string
21
15
  /** Force the subtitle on/off; defaults to container-responsive (hidden below ~1024px). */
22
16
  showSubtitle?: boolean
17
+ /**
18
+ * Replace the whole brand region. Wins over `brand` / `subtitle`, and opts out
19
+ * of the responsive subtitle collapse — the node owns its own behaviour.
20
+ */
21
+ leading?: ReactNode
22
+ /**
23
+ * App chrome for the right cluster, left → right. An array is rendered with the
24
+ * bar's own vertical dividers between the items, so an app supplies its
25
+ * controls and the shell keeps the divider rhythm.
26
+ */
27
+ trailing?: ReactNode | ReactNode[]
28
+ /** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
29
+ time?: number | Date
23
30
  /** Force the clock on/off; defaults to container-responsive (hidden below ~720px). */
24
31
  showClock?: boolean
25
- onSelectDevice?: (device: Device) => void
26
32
  className?: string
27
33
  }
28
34
 
@@ -30,20 +36,39 @@ export interface TopBarProps {
30
36
  const SUBTITLE_MIN = 1024
31
37
  const CLOCK_MIN = 720
32
38
 
39
+ function ClusterDivider() {
40
+ return <Divider orientation="vertical" className="h-4 bg-border-prominent" />
41
+ }
42
+
43
+ /** Interleave the bar's divider between slot items, skipping empty slots. */
44
+ function dividedCluster(items: ReactNode[]) {
45
+ return items
46
+ .filter((item) => item !== null && item !== undefined && item !== false)
47
+ .map((item, i) => (
48
+ <Fragment key={i}>
49
+ {i > 0 ? <ClusterDivider /> : null}
50
+ {item}
51
+ </Fragment>
52
+ ))
53
+ }
54
+
33
55
  /**
34
- * S1 · TopBar — the persistent shell chrome band. Brand (left) + a right cluster
35
- * of session state · device glyph · clock (clock edge-pinned so spacing stays
36
- * stable as the state text changes). Collapses subtitle then clock as its
37
- * container narrows.
56
+ * S1 · TopBar — the persistent shell chrome band, generic over the app. Brand
57
+ * (left) + a `trailing` cluster of app-supplied chrome and the wall clock (clock
58
+ * edge-pinned so spacing stays stable as the items' text changes). Collapses
59
+ * subtitle then clock as its container narrows.
60
+ *
61
+ * @example
62
+ * <TopBar brand="brain" trailing={[<IndexStatus />, <SearchScope />]} />
38
63
  */
39
64
  export function TopBar({
40
- state,
41
- devices,
42
- time,
43
- subtitle = 'wall dashboard',
65
+ brand = 'voltras',
66
+ subtitle,
44
67
  showSubtitle,
68
+ leading,
69
+ trailing,
70
+ time,
45
71
  showClock,
46
- onSelectDevice,
47
72
  className,
48
73
  }: TopBarProps) {
49
74
  const [width, setWidth] = useState(SUBTITLE_MIN)
@@ -52,6 +77,18 @@ export function TopBar({
52
77
  const subtitleVisible = showSubtitle ?? width >= SUBTITLE_MIN
53
78
  const clockVisible = showClock ?? width >= CLOCK_MIN
54
79
 
80
+ const clock = clockVisible ? (
81
+ <DateTime
82
+ value={time}
83
+ live={time == null}
84
+ format="time"
85
+ hour12={false}
86
+ variant="mono"
87
+ color="secondary"
88
+ className="text-[11px] min-w-[38px] text-right"
89
+ />
90
+ ) : null
91
+
55
92
  return (
56
93
  <View
57
94
  onLayout={onLayout}
@@ -62,27 +99,11 @@ export function TopBar({
62
99
  className
63
100
  )}
64
101
  >
65
- <BrandLockup subtitle={subtitle} showSubtitle={subtitleVisible} />
102
+ {leading ?? <BrandLockup brand={brand} subtitle={subtitle} showSubtitle={subtitleVisible} />}
66
103
 
67
- {/* right cluster — order: state · device · time (time pinned to the edge) */}
104
+ {/* right cluster — app chrome then the clock, pinned to the edge */}
68
105
  <View className="ml-auto flex-row items-center gap-[12px]">
69
- <SessionStatePill state={state} />
70
- <Divider orientation="vertical" className="h-4 bg-border-prominent" />
71
- <DeviceMenu devices={devices} onSelectDevice={onSelectDevice} />
72
- {clockVisible ? (
73
- <>
74
- <Divider orientation="vertical" className="h-4 bg-border-prominent" />
75
- <DateTime
76
- value={time}
77
- live={time == null}
78
- format="time"
79
- hour12={false}
80
- variant="mono"
81
- color="secondary"
82
- className="text-[11px] min-w-[38px] text-right"
83
- />
84
- </>
85
- ) : null}
106
+ {dividedCluster([...(Array.isArray(trailing) ? trailing : [trailing]), clock])}
86
107
  </View>
87
108
  </View>
88
109
  )
@@ -0,0 +1,66 @@
1
+ import type { ReactNode } from 'react'
2
+ import { BotIcon, BrainIcon, HeadphonesIcon, KanbanIcon, VoltrasMark } from '../icons'
3
+
4
+ /** The apps that are expected to mount this shell. */
5
+ export type BrandKey = 'voltras' | 'audiobook' | 'active-work' | 'agents' | 'brain'
6
+
7
+ export interface BrandPreset {
8
+ /** The mark glyph, rendered at 14px through the accent token via `currentColor`. */
9
+ mark: ReactNode
10
+ /** Uppercase product wordmark. */
11
+ wordmark: string
12
+ /** Semantic text token that colours the mark. */
13
+ accentClassName: string
14
+ /**
15
+ * The same accent as a background token, for the nav's active bar. Declared as
16
+ * a literal alongside its `text-*` twin because Tailwind only emits classes it
17
+ * can see in the source — a name built at runtime is never generated.
18
+ */
19
+ accentBarClassName: string
20
+ /** Default "/ subtitle" for that app's shell. */
21
+ subtitle: string
22
+ }
23
+
24
+ // Accents come from `data-*` rather than `status-*`: `data-*` is the library's
25
+ // palette of N distinct, CVD-checked hues with no semantic load, which is exactly
26
+ // what a per-app identity accent needs. Voltras keeps the real brand token.
27
+ export const brandPresets: Record<BrandKey, BrandPreset> = {
28
+ voltras: {
29
+ mark: <VoltrasMark size={14} color="currentColor" />,
30
+ wordmark: 'VOLTRAS',
31
+ accentClassName: 'text-brand-primary',
32
+ accentBarClassName: 'bg-brand-primary',
33
+ subtitle: 'wall dashboard',
34
+ },
35
+ audiobook: {
36
+ mark: <HeadphonesIcon size={14} color="currentColor" />,
37
+ wordmark: 'AUDIOBOOK',
38
+ accentClassName: 'text-data-5',
39
+ accentBarClassName: 'bg-data-5',
40
+ subtitle: 'library',
41
+ },
42
+ 'active-work': {
43
+ mark: <KanbanIcon size={14} color="currentColor" />,
44
+ wordmark: 'ACTIVE WORK',
45
+ accentClassName: 'text-data-1',
46
+ accentBarClassName: 'bg-data-1',
47
+ subtitle: 'initiatives',
48
+ },
49
+ agents: {
50
+ mark: <BotIcon size={14} color="currentColor" />,
51
+ wordmark: 'AGENTS',
52
+ accentClassName: 'text-data-2',
53
+ accentBarClassName: 'bg-data-2',
54
+ subtitle: 'fleet',
55
+ },
56
+ brain: {
57
+ mark: <BrainIcon size={14} color="currentColor" />,
58
+ wordmark: 'BRAIN',
59
+ accentClassName: 'text-data-3',
60
+ accentBarClassName: 'bg-data-3',
61
+ subtitle: 'knowledge',
62
+ },
63
+ }
64
+
65
+ /** Every brand key, in the order the stories present them. */
66
+ export const brandKeys = Object.keys(brandPresets) as BrandKey[]
@@ -1,13 +1,14 @@
1
- // Shell chrome — persistent dashboard frame (nav / top bar / rail / drawers / strip).
2
- // S1 · Top bar family. (Icons live in the shared `components/icons` primitive.)
1
+ // Shell chrome — the GENERIC persistent app frame (nav / top bar / brand).
2
+ // App-specific chrome composes this family; it never leaks back into it.
3
+ // (Icons live in the shared `components/icons` primitive.)
4
+ export * from './brands'
3
5
  export * from './BrandLockup'
4
- export * from './SessionStatePill'
5
- export * from './DeviceIndicator'
6
- export * from './DeviceRow'
7
- export * from './DeviceMenu'
6
+ // S1 · Top bar family.
8
7
  export * from './TopBar'
9
8
  // S2 · Side nav family.
10
9
  export * from './NavItem'
11
10
  export * from './SideNav'
12
- // S3 · Dashboard shell — composes the nav + top-bar chrome over a content slot.
13
- export * from './DashboardShell'
11
+ // S3 · App shell — composes the nav + top-bar chrome over a content slot.
12
+ export * from './AppShell'
13
+ // AW-132 · Workout-app shell, composed over the generic family above.
14
+ export * from './workout'
@@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { DeviceIndicator } from './DeviceIndicator'
3
3
 
4
4
  const meta: Meta<typeof DeviceIndicator> = {
5
- title: 'Shell/DeviceIndicator',
5
+ title: 'Shell/Workout/DeviceIndicator',
6
6
  component: DeviceIndicator,
7
- tags: ['autodocs'],
7
+ tags: ['autodocs', 'status:candidate', '!status:review'],
8
8
  args: { status: 'connected' },
9
9
  argTypes: {
10
10
  status: { control: 'select', options: ['connected', 'degraded', 'lost'] },
@@ -1,6 +1,6 @@
1
1
  import { View, Pressable, type ViewProps } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { BluetoothIcon } from '../icons'
2
+ import { cn } from '../../../utils/cn'
3
+ import { BluetoothIcon } from '../../icons'
4
4
 
5
5
  export type DeviceConnState = 'connected' | 'degraded' | 'lost'
6
6
 
@@ -10,9 +10,9 @@ const DEVICES: Device[] = [
10
10
  ]
11
11
 
12
12
  const meta: Meta<typeof DeviceMenu> = {
13
- title: 'Shell/DeviceMenu',
13
+ title: 'Shell/Workout/DeviceMenu',
14
14
  component: DeviceMenu,
15
- tags: ['autodocs'],
15
+ tags: ['autodocs', 'status:candidate', '!status:review'],
16
16
  args: { devices: DEVICES, isOpen: false },
17
17
  argTypes: {
18
18
  devices: { control: 'object' },
@@ -1,7 +1,7 @@
1
1
  import { View } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { Typography } from '../custom/Typography'
4
- import { Popover, PopoverTrigger, PopoverContent } from '../ui/popover'
2
+ import { cn } from '../../../utils/cn'
3
+ import { Typography } from '../../custom/Typography'
4
+ import { Popover, PopoverTrigger, PopoverContent } from '../../ui/popover'
5
5
  import { DeviceIndicator, type DeviceConnState } from './DeviceIndicator'
6
6
  import { DeviceRow, type Device } from './DeviceRow'
7
7
 
@@ -3,9 +3,9 @@ import { View } from 'react-native'
3
3
  import { DeviceRow } from './DeviceRow'
4
4
 
5
5
  const meta: Meta<typeof DeviceRow> = {
6
- title: 'Shell/DeviceRow',
6
+ title: 'Shell/Workout/DeviceRow',
7
7
  component: DeviceRow,
8
- tags: ['autodocs'],
8
+ tags: ['autodocs', 'status:candidate', '!status:review'],
9
9
  args: {
10
10
  device: { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
11
11
  },
@@ -1,7 +1,7 @@
1
1
  import { View, Pressable, type ViewProps } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { Indicator, type IndicatorColor } from '../ui/indicator'
4
- import { Typography } from '../custom/Typography'
2
+ import { cn } from '../../../utils/cn'
3
+ import { Indicator, type IndicatorColor } from '../../ui/indicator'
4
+ import { Typography } from '../../custom/Typography'
5
5
 
6
6
  export type DeviceRowState = 'connected' | 'available' | 'degraded' | 'lost'
7
7
 
@@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { SessionStatePill } from './SessionStatePill'
3
3
 
4
4
  const meta: Meta<typeof SessionStatePill> = {
5
- title: 'Shell/SessionStatePill',
5
+ title: 'Shell/Workout/SessionStatePill',
6
6
  component: SessionStatePill,
7
- tags: ['autodocs'],
7
+ tags: ['autodocs', 'status:candidate', '!status:review'],
8
8
  args: { state: 'live' },
9
9
  argTypes: {
10
10
  state: { control: 'select', options: ['live', 'rest', 'idle'] },
@@ -1,7 +1,7 @@
1
1
  import { View, type ViewProps } from 'react-native'
2
- import { cn } from '../../utils/cn'
3
- import { Indicator, type IndicatorColor, type IndicatorPulse } from '../ui/indicator'
4
- import { Typography, type TypographyColor } from '../custom/Typography'
2
+ import { cn } from '../../../utils/cn'
3
+ import { Indicator, type IndicatorColor, type IndicatorPulse } from '../../ui/indicator'
4
+ import { Typography, type TypographyColor } from '../../custom/Typography'
5
5
 
6
6
  export type SessionState = 'live' | 'rest' | 'idle'
7
7
 
@@ -32,6 +32,8 @@ const stateConfig: Record<
32
32
  * The global session-state readout (dot + label). Shared: the top bar's status
33
33
  * and the Live-view header both use it (this is the ledger's "StatusPill",
34
34
  * Family C — built once). S1 · SessionStatePill.
35
+ *
36
+ * @deprecated Use `<Pill tone="…" leading="dot">` — removed after AW-127 consumer migration.
35
37
  */
36
38
  export function SessionStatePill({ state, label, className, ...props }: SessionStatePillProps) {
37
39
  const cfg = stateConfig[state]
@@ -0,0 +1,71 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { WorkoutShell } from './WorkoutShell'
4
+ import { Surface } from '../../ui/surface'
5
+ import { Typography } from '../../custom/Typography'
6
+
7
+ /**
8
+ * `Pages/WorkoutShell` — the workout wall-dashboard chrome as one registered surface.
9
+ * Composes the generic AppShell and fills its slots with the workout's own chrome, so
10
+ * the dashboard app mounts a single component.
11
+ */
12
+ const meta: Meta<typeof WorkoutShell> = {
13
+ title: 'Pages/WorkoutShell',
14
+ component: WorkoutShell,
15
+ tags: ['autodocs', 'status:candidate', '!status:review'],
16
+ parameters: {
17
+ layout: 'fullscreen',
18
+ docs: {
19
+ description: {
20
+ component:
21
+ '**Page shell (workout app).** Composes ' +
22
+ '[AppShell](?path=/docs/pages-appshell--docs) and fills its top-bar slot with ' +
23
+ '[WorkoutTopBar](?path=/docs/shell-workout-workouttopbar--docs) — which pulls in ' +
24
+ 'DeviceMenu, DeviceRow, DeviceIndicator and SessionStatePill — and its nav slot with ' +
25
+ 'the four workout categories. Mount this instead of re-assembling the chrome per ' +
26
+ 'view.\n\nThis is the AW-132 shape: the generic shell knows nothing about devices or ' +
27
+ 'session state; this component is the whole workout-specific surface.',
28
+ },
29
+ },
30
+ },
31
+ argTypes: {
32
+ state: { control: 'select', options: ['live', 'rest', 'idle'] },
33
+ activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
34
+ liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
35
+ onNavigate: { control: false },
36
+ onSelectDevice: { control: false },
37
+ },
38
+ decorators: [
39
+ (Story) => (
40
+ <Surface level="base" style={{ height: '100vh' as unknown as number }}>
41
+ <Story />
42
+ </Surface>
43
+ ),
44
+ ],
45
+ }
46
+
47
+ export default meta
48
+ type Story = StoryObj<typeof WorkoutShell>
49
+
50
+ export const Default: Story = {
51
+ args: { activeKey: 'live', state: 'live' },
52
+ }
53
+
54
+ export const WithContent: Story = {
55
+ args: { activeKey: 'review', state: 'rest', liveKey: 'live' },
56
+ render: (args) => (
57
+ <WorkoutShell {...args}>
58
+ <View className="flex-1 gap-1.5 p-7">
59
+ <Typography variant="h4" color="primary">
60
+ Review
61
+ </Typography>
62
+ <Typography variant="body2" color="tertiary">
63
+ A page mounts its content here; the shell keeps the nav + top-bar chrome persistent.
64
+ </Typography>
65
+ </View>
66
+ </WorkoutShell>
67
+ ),
68
+ parameters: {
69
+ docs: { description: { story: 'The shell with a page mounted in its content slot.' } },
70
+ },
71
+ }
@@ -2,11 +2,11 @@ import { describe, it, expect, vi } from 'vitest'
2
2
  import { render, screen, fireEvent } from '@testing-library/react'
3
3
  import { Text } from 'react-native'
4
4
  import { axe } from 'jest-axe'
5
- import { DashboardShell } from './DashboardShell'
5
+ import { WorkoutShell, DashboardShell } from './WorkoutShell'
6
6
 
7
- describe('DashboardShell', () => {
7
+ describe('WorkoutShell', () => {
8
8
  it('composes the SideNav rail and its categories', () => {
9
- render(<DashboardShell activeKey="live" />)
9
+ render(<WorkoutShell activeKey="live" />)
10
10
  expect(screen.getByRole('tablist')).toBeInTheDocument()
11
11
  ;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
12
12
  expect(screen.getByRole('tab', { name })).toBeInTheDocument()
@@ -14,15 +14,15 @@ describe('DashboardShell', () => {
14
14
  })
15
15
 
16
16
  it('renders the content-slot placeholder when no children are given', () => {
17
- render(<DashboardShell activeKey="live" />)
17
+ render(<WorkoutShell activeKey="live" />)
18
18
  expect(screen.getByText('main content region')).toBeInTheDocument()
19
19
  })
20
20
 
21
21
  it('mounts children in the content slot and drops the placeholder', () => {
22
22
  render(
23
- <DashboardShell activeKey="review">
23
+ <WorkoutShell activeKey="review">
24
24
  <Text>my page body</Text>
25
- </DashboardShell>
25
+ </WorkoutShell>
26
26
  )
27
27
  expect(screen.getByText('my page body')).toBeInTheDocument()
28
28
  expect(screen.queryByText('main content region')).not.toBeInTheDocument()
@@ -30,13 +30,24 @@ describe('DashboardShell', () => {
30
30
 
31
31
  it('forwards nav taps through onNavigate', () => {
32
32
  const onNavigate = vi.fn()
33
- render(<DashboardShell activeKey="live" onNavigate={onNavigate} />)
33
+ render(<WorkoutShell activeKey="live" onNavigate={onNavigate} />)
34
34
  fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
35
35
  expect(onNavigate).toHaveBeenCalledWith('body')
36
36
  })
37
37
 
38
+ it('fills the generic top-bar slot with the workout chrome', () => {
39
+ render(<WorkoutShell activeKey="live" state="rest" />)
40
+ expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
41
+ expect(screen.getByText('REST')).toBeInTheDocument()
42
+ expect(screen.getByRole('button', { name: 'Devices' })).toBeInTheDocument()
43
+ })
44
+
45
+ it('still exports the pre-AW-132 DashboardShell name', () => {
46
+ expect(DashboardShell).toBe(WorkoutShell)
47
+ })
48
+
38
49
  it('has no a11y violations', async () => {
39
- const { container } = render(<DashboardShell activeKey="live" />)
50
+ const { container } = render(<WorkoutShell activeKey="live" />)
40
51
  expect(await axe(container)).toHaveNoViolations()
41
52
  })
42
53
  })
@@ -0,0 +1,77 @@
1
+ import type { ReactNode } from 'react'
2
+ import { AppShell } from '../AppShell'
3
+ import { type SideNavItem } from '../SideNav'
4
+ import { WorkoutTopBar } from './WorkoutTopBar'
5
+ import { workoutNavItems } from './workoutNavItems'
6
+ import { type Device } from './DeviceRow'
7
+ import { type SessionState } from './SessionStatePill'
8
+
9
+ /**
10
+ * `WorkoutShell` — the workout wall-dashboard chrome. Composes the generic
11
+ * {@link AppShell} and fills its top-bar slot with the workout's own
12
+ * {@link WorkoutTopBar} (session state + device menu) and its nav slot with the
13
+ * four workout categories. The generic shell knows none of this; this component
14
+ * is the whole workout-specific surface. Driven entirely by props.
15
+ */
16
+ export interface WorkoutShellProps {
17
+ /** Active nav category key. */
18
+ activeKey?: string
19
+ /** Nav categories, top → bottom. Defaults to the four workout categories. */
20
+ navItems?: SideNavItem[]
21
+ /** A category with off-view live activity → a quiet cue on that item. */
22
+ liveKey?: string | null
23
+ /** Global session state → the TopBar status pill. */
24
+ state?: SessionState
25
+ /** Connected devices → the TopBar connection glyph + dropdown. */
26
+ devices?: Device[]
27
+ /** TopBar brand subtitle. */
28
+ subtitle?: string
29
+ onNavigate?: (key: string) => void
30
+ onSelectDevice?: (device: Device) => void
31
+ /** Main content region. A placeholder renders when omitted. */
32
+ children?: ReactNode
33
+ className?: string
34
+ }
35
+
36
+ const DEMO_DEVICES: Device[] = [
37
+ { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
38
+ { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
39
+ ]
40
+
41
+ export function WorkoutShell({
42
+ activeKey = 'live',
43
+ navItems = workoutNavItems,
44
+ liveKey = null,
45
+ state = 'live',
46
+ devices = DEMO_DEVICES,
47
+ subtitle = 'wall dashboard',
48
+ onNavigate,
49
+ onSelectDevice,
50
+ children,
51
+ className,
52
+ }: WorkoutShellProps) {
53
+ return (
54
+ <AppShell
55
+ className={className}
56
+ navItems={navItems}
57
+ activeKey={activeKey}
58
+ liveKey={liveKey}
59
+ onNavigate={onNavigate}
60
+ topBar={
61
+ <WorkoutTopBar
62
+ state={state}
63
+ devices={devices}
64
+ subtitle={subtitle}
65
+ onSelectDevice={onSelectDevice}
66
+ />
67
+ }
68
+ >
69
+ {children}
70
+ </AppShell>
71
+ )
72
+ }
73
+
74
+ /** @deprecated Renamed to `WorkoutShell` — same props (AW-132). Removed after consumer migration. */
75
+ export const DashboardShell = WorkoutShell
76
+ /** @deprecated Renamed to `WorkoutShellProps` (AW-132). */
77
+ export type DashboardShellProps = WorkoutShellProps
@@ -0,0 +1,58 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { WorkoutTopBar } from './WorkoutTopBar'
3
+ import { type Device } from './DeviceRow'
4
+
5
+ const DEVICES: Device[] = [
6
+ { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
7
+ { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
8
+ { id: 'Voltra-77E0', nickname: 'Spare', slot: null, state: 'available' },
9
+ ]
10
+
11
+ const meta: Meta<typeof WorkoutTopBar> = {
12
+ title: 'Shell/Workout/WorkoutTopBar',
13
+ component: WorkoutTopBar,
14
+ tags: ['autodocs', 'status:candidate', '!status:review'],
15
+ args: { state: 'live', devices: DEVICES },
16
+ argTypes: {
17
+ state: { control: 'select', options: ['live', 'rest', 'idle'] },
18
+ devices: { control: 'object' },
19
+ subtitle: { control: 'text' },
20
+ showSubtitle: { control: 'boolean' },
21
+ showClock: { control: 'boolean' },
22
+ time: { control: false },
23
+ onSelectDevice: { control: false },
24
+ },
25
+ parameters: {
26
+ layout: 'fullscreen',
27
+ docs: {
28
+ description: {
29
+ component:
30
+ '**Organism (workout app).** The generic ' +
31
+ "[TopBar](?path=/docs/shell-topbar--docs) with the workout's own chrome in its " +
32
+ '`trailing` slot: [SessionStatePill](?path=/docs/shell-workout-sessionstatepill--docs) ' +
33
+ 'then [DeviceMenu](?path=/docs/shell-workout-devicemenu--docs). The bar adds the ' +
34
+ 'dividers and the edge-pinned clock; this component only supplies the items ' +
35
+ '(AW-132).\n\n' +
36
+ '**Try it:** use the **Controls** to change `state`, edit `devices` (set one to ' +
37
+ '`lost` to see the fault), or toggle `showSubtitle` / `showClock`. **Resize the ' +
38
+ 'canvas** to watch the container-responsive collapse (SIZE-D01). Click the device ' +
39
+ 'glyph to open the menu.',
40
+ },
41
+ },
42
+ },
43
+ }
44
+ export default meta
45
+ type Story = StoryObj<typeof WorkoutTopBar>
46
+
47
+ export const Default: Story = {}
48
+
49
+ /** A lost device — the glyph alone carries the fault. */
50
+ export const DeviceLost: Story = {
51
+ args: {
52
+ state: 'rest',
53
+ devices: [
54
+ { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
55
+ { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'lost' },
56
+ ],
57
+ },
58
+ }
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { WorkoutTopBar } from './WorkoutTopBar'
5
+ import { type Device } from './DeviceRow'
6
+
7
+ const devices: Device[] = [
8
+ { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
9
+ { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
10
+ ]
11
+
12
+ const AT_1612 = new Date(2024, 0, 1, 16, 12) // local 16:12
13
+
14
+ describe('WorkoutTopBar', () => {
15
+ it('renders brand, session state, the device glyph and a 24h clock', () => {
16
+ render(<WorkoutTopBar state="live" devices={devices} time={AT_1612} showClock />)
17
+ expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
18
+ expect(screen.getByText('LIVE')).toBeInTheDocument()
19
+ expect(screen.getByRole('button', { name: 'Devices' })).toBeInTheDocument()
20
+ expect(screen.getByText('16:12')).toBeInTheDocument()
21
+ })
22
+
23
+ it('hides the clock when showClock is false', () => {
24
+ render(<WorkoutTopBar state="live" devices={devices} time={AT_1612} showClock={false} />)
25
+ expect(screen.queryByText('16:12')).not.toBeInTheDocument()
26
+ })
27
+
28
+ it('hides the subtitle when showSubtitle is false', () => {
29
+ render(<WorkoutTopBar state="rest" devices={devices} showSubtitle={false} />)
30
+ expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
31
+ })
32
+
33
+ it('has no accessibility violations', async () => {
34
+ const { container } = render(
35
+ <WorkoutTopBar state="live" devices={devices} time={AT_1612} showClock />
36
+ )
37
+ expect(await axe(container)).toHaveNoViolations()
38
+ })
39
+ })