@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,22 +1,54 @@
1
1
  # Shell chrome — component family
2
2
 
3
- The persistent dashboard frame (top bar, and — as the decomposition proceeds — side nav, session
4
- rail, drawers, pinned strip). Components live flat on disk; the tiering below is a documentation
5
- contract, not a directory layout, so promoting/composing is pure import churn.
3
+ The persistent dashboard frame (top bar, side nav, and — as the decomposition proceeds — session
4
+ rail, drawers, pinned strip). The tiering below is a documentation contract, not a directory
5
+ layout, so promoting/composing is pure import churn.
6
6
 
7
7
  This README is the **index**: it maps each component's dependencies (**composes ↓**) and its consumers
8
8
  (**used-by ↑**) so you can navigate the tree in both directions and spot where a hand-rolled element
9
9
  should be reaching for an existing primitive instead. Storybook mirrors this tree under `Shell/TopBar/…`,
10
10
  and every component's autodocs page repeats its "Composes" links.
11
11
 
12
+ ## The generic/app split (AW-132)
13
+
14
+ `shell/` is **generic**. It knows about a band, a rail, a brand lockup and a content region, and
15
+ nothing about any one product. `shell/workout/` is the **workout app's** chrome, and it composes
16
+ the generic family. The dependency arrow points one way only: nothing in `shell/` may import from
17
+ `shell/workout/`, and a second app (audiobook, active-work, agent dashboards, brain) gets its own
18
+ sibling directory rather than a new prop on `AppShell`.
19
+
20
+ **The composition mechanism is the library's existing slot idiom** — named `ReactNode` props with a
21
+ built-in default, the same shape as `Pill`'s `leading`/`trailing` and `SectionHeader`'s `trailing`.
22
+ No second mechanism was introduced.
23
+
24
+ | Slot | On | Default | An app uses it to… |
25
+ | ------------------------ | ----------- | -------------------------------- | ---------------------------------------- |
26
+ | `TopBar.trailing` | `TopBar` | just the clock | put its own chrome in the right cluster |
27
+ | `TopBar.leading` | `TopBar` | `<BrandLockup brand=…>` | replace the brand region outright |
28
+ | `AppShell.topBarTrailing` | `AppShell` | — | pass chrome through to the default bar |
29
+ | `AppShell.topBar` | `AppShell` | `<TopBar brand=…>` | swap the whole band |
30
+ | `AppShell.nav` | `AppShell` | `<SideNav items=…>` | swap the whole rail |
31
+ | `AppShell.children` | `AppShell` | a placeholder | mount its page |
32
+
33
+ `TopBar.trailing` takes an array as well as a node. **The bar interleaves its own vertical dividers
34
+ between the items**, so an app supplies the controls and the shell keeps the divider rhythm from the
35
+ S1 lock — the app never hand-places a `Divider`, and can't drift from it.
36
+
37
+ Two things stopped being generic defaults and became the workout app's data:
38
+
39
+ - `SideNav.items` is **required**. The four Live · Review · Plan · Body categories are
40
+ `workoutNavItems` in `shell/workout/`.
41
+ - `BrandLockup` is generic over `brand` (five presets in `brands.tsx`), rather than hard-coding
42
+ the Voltras mark and wordmark.
43
+
12
44
  ## Specimen: `SideNav` (the second shell unit, S2)
13
45
 
14
46
  ```
15
47
  SideNav ...................... organism — the 60px left rail (Shell/SideNav)
16
- ├─ NavItem ................... molecule × 4 (Live · Review · Plan · Body)
17
- │ ├─ icon ................... icons (shared primitive) — Activity/History/Layers/PersonStanding
48
+ ├─ NavItem ................... molecule × `items` (the app's own categories)
49
+ │ ├─ icon ................... icons (shared primitive) — supplied per item
18
50
  │ └─ Typography ............. titan · button variant (uppercase micro-label)
19
- └─ (accent bar) .............. bg-brand-primary edge bar on the active item
51
+ └─ (accent bar) .............. edge bar on the active item, in the app's brand accent
20
52
  ```
21
53
 
22
54
  Decisions locked 2026-07-08 (specimen `sources/design/shell/S2-sidenav/`): lucide glyphs
@@ -24,41 +56,58 @@ Decisions locked 2026-07-08 (specimen `sources/design/shell/S2-sidenav/`): lucid
24
56
  bar** (short, centered on the icon+label) · **60px** icon+micro-label · live cue = **muted-green label**
25
57
  (`status-live-muted`) on the Live item while a set runs off-Live · four items, no footer, no top-nav. Fixed
26
58
  60px at every width (labels sit under the glyph). `SideNav` is presentational — `activeKey` / `onNavigate` /
27
- `liveKey`; the app owns routing + which key is live.
59
+ `liveKey`; the app owns routing + which key is live. Those four categories are now
60
+ `workoutNavItems` in `shell/workout/` (AW-132); the rail itself has no built-in set.
28
61
 
29
62
  ## Specimen: `TopBar` (the first shell unit, S1)
30
63
 
31
64
  ```
32
65
  TopBar ....................... organism — the chrome band (Shell/TopBar)
33
- ├─ BrandLockup ............... molecule
34
- │ ├─ VoltrasMark ............ icons (shared primitive)
66
+ ├─ BrandLockup ............... molecule — default `leading`
67
+ │ ├─ brand mark ............. icons (shared primitive) — per `brands.tsx` preset
35
68
  │ └─ Typography ............. titan · button variant
36
- ├─ SessionStatePill .......... molecule (= the reusable StatusPill)
37
- ├─ Indicator .............. titan · ping pulse + vivid color
38
- │ └─ Typography ............. titan · monoLabel
39
- ├─ Divider ................... titan · bg-border-prominent
40
- ├─ DeviceMenu ................ organism
41
- │ ├─ Popover ................ titan
42
- │ ├─ DeviceIndicator ........ molecule → BluetoothIcon (icons primitive)
43
- │ ├─ DeviceRow .............. molecule → Indicator + Typography (body2 / mono)
44
- │ └─ Typography ............. titan · monoLabel (header)
45
- ├─ DateTime .................. titan · mono variant, live 24h clock
69
+ ├─ trailing[] ................ SLOT — app chrome, divider-interleaved by the bar
70
+ ├─ Divider ................... titan · bg-border-prominent (one per slot gap)
71
+ ├─ DateTime .................. titan · mono variant, live 24h clock (edge-pinned)
46
72
  └─ surfaceGradient.chrome .... theme · gradient primitive
47
73
  ```
48
74
 
49
- ## Dependency map
75
+ ## Specimen: `WorkoutTopBar` (the same band, filled by the workout app)
76
+
77
+ ```
78
+ WorkoutTopBar ................ organism — Shell/Workout/WorkoutTopBar
79
+ └─ TopBar .................... generic band, brand="voltras"
80
+ └─ trailing[] ............. supplied by the workout app:
81
+ ├─ SessionStatePill .... molecule → Indicator + Typography (monoLabel)
82
+ └─ DeviceMenu .......... organism
83
+ ├─ Popover .......... titan
84
+ ├─ DeviceIndicator .. molecule → BluetoothIcon (icons primitive)
85
+ ├─ DeviceRow ........ molecule → Indicator + Typography (body2 / mono)
86
+ └─ Typography ....... titan · monoLabel (header)
87
+ ```
88
+
89
+ ## Dependency map — generic (`shell/`)
50
90
 
51
91
  | Component | Tier | Composes ↓ | Used-by ↑ |
52
92
  |---|---|---|---|
53
- | `SideNav` | organism | NavItem ×4, `components/icons` (Activity/History/Layers/PersonStanding) | dashboard app root *(planned)* |
93
+ | `AppShell` | page shell | TopBar, SideNav, Surface | WorkoutShell, any app root |
94
+ | `SideNav` | organism | NavItem × `items` | AppShell |
54
95
  | `NavItem` | molecule | icon, Typography | SideNav |
55
- | `TopBar` | organism | BrandLockup, SessionStatePill, Divider, DeviceMenu, DateTime, `surfaceGradient.chrome` | dashboard app root *(planned)* |
56
- | `BrandLockup` | molecule | VoltrasMark, Typography | TopBar |
57
- | `SessionStatePill` | molecule | Indicator, Typography | TopBar, **Live-view header** *(planned reuse)* |
58
- | `DeviceMenu` | organism | Popover, DeviceIndicator, DeviceRow, Typography | TopBar |
96
+ | `TopBar` | organism | BrandLockup, Divider, DateTime, `surfaceGradient.chrome` | AppShell, WorkoutTopBar |
97
+ | `BrandLockup` | molecule | a brand mark icon, Typography | TopBar |
98
+ | `brands.tsx` | data | VoltrasMark / Headphones / Kanban / Bot / Brain icons | BrandLockup, TopBar, AppShell |
99
+
100
+ ## Dependency map — workout app (`shell/workout/`)
101
+
102
+ | Component | Tier | Composes ↓ | Used-by ↑ |
103
+ |---|---|---|---|
104
+ | `WorkoutShell` | page shell | AppShell, WorkoutTopBar, workoutNavItems | dashboard app root |
105
+ | `WorkoutTopBar` | organism | TopBar, SessionStatePill, DeviceMenu | WorkoutShell |
106
+ | `SessionStatePill` | molecule | Indicator, Typography | WorkoutTopBar, **Live-view header** *(planned reuse)* |
107
+ | `DeviceMenu` | organism | Popover, DeviceIndicator, DeviceRow, Typography | WorkoutTopBar |
59
108
  | `DeviceIndicator` | molecule | BluetoothIcon | DeviceMenu |
60
109
  | `DeviceRow` | molecule | Indicator, Typography | DeviceMenu |
61
- | `VoltrasMark` / `BluetoothIcon` | **`components/icons`** primitive | `SvgIcon` base | BrandLockup / DeviceIndicator (+ any consumer) |
110
+ | `workoutNavItems` | data | Activity/History/Layers/PersonStanding icons | WorkoutShell |
62
111
 
63
112
  ## Shared substrates introduced here (reusable beyond the shell)
64
113
 
@@ -98,6 +147,20 @@ Every leaf now composes a primitive rather than hand-rolling it:
98
147
  - **Lint guardrails** — components may not inline `linear-gradient` (use `surfaceGradient`); shell + icons may
99
148
  not use raw hex (use tokens).
100
149
 
150
+ **AW-132 shared substrate:** four brand marks added to `components/icons` (`HeadphonesIcon`,
151
+ `KanbanIcon`, `BotIcon`, `BrainIcon`), so `brands.tsx` can carry a preset for every app expected to
152
+ mount this shell. Brand accents come from `data-*` rather than `status-*`: `data-*` is the library's
153
+ set of distinct, CVD-checked hues with no semantic load, which is what a per-app identity accent
154
+ needs. Voltras keeps the real `brand-primary` token.
155
+
156
+ **The nav accent is brand-level, not shell-level** (operator, Gate 2 2026-09-11). `SideNav`'s active
157
+ item and its edge bar take the mounting app's accent, so a Brain shell reads yellow throughout
158
+ rather than showing a Voltras-orange active item under a yellow lockup. `AppShell` threads it down
159
+ from `brand`; a bare `SideNav` takes `accentClassName` / `accentBarClassName`. Each preset declares
160
+ both as literals (`text-data-3` + `bg-data-3`) because Tailwind only emits classes it can see in the
161
+ source — a name built at runtime is never generated — and a test asserts the pair always matches.
162
+ Workout is unaffected: its accent *is* `brand-primary`.
163
+
101
164
  **S2 shared substrate:** four nav glyphs added to `components/icons` (`ActivityIcon`, `HistoryIcon`,
102
165
  `LayersIcon`, `PersonStandingIcon` — lucide-mirrored, like Dumbbell/Star), available system-wide.
103
166
 
@@ -3,15 +3,20 @@ import { useArgs } from 'storybook/preview-api'
3
3
  import { View } from 'react-native'
4
4
  import { SideNav } from './SideNav'
5
5
  import { Typography } from '../custom/Typography'
6
+ import { Surface } from '../ui/surface'
7
+ import { BotIcon, BrainIcon, LayersIcon } from '../icons'
8
+ import { workoutNavItems } from './workout'
9
+ import { brandPresets } from './brands'
6
10
 
7
11
  const meta: Meta<typeof SideNav> = {
8
12
  title: 'Shell/SideNav',
9
13
  component: SideNav,
10
- tags: ['autodocs'],
11
- args: { activeKey: 'live', liveKey: null },
14
+ tags: ['autodocs', 'status:candidate', '!status:review'],
15
+ args: { items: workoutNavItems, activeKey: 'live', liveKey: null },
12
16
  argTypes: {
13
17
  activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
14
18
  liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
19
+ items: { control: false },
15
20
  onNavigate: { action: 'navigate' },
16
21
  },
17
22
  // Interactive AND Controls-synced: clicking a category writes `activeKey` back to the
@@ -35,14 +40,14 @@ const meta: Meta<typeof SideNav> = {
35
40
  // stretches with the viewport (resize the canvas height to see it hold).
36
41
  decorators: [
37
42
  (Story) => (
38
- <View className="min-h-screen flex-row bg-background-default">
43
+ <Surface level="base" className="min-h-screen flex-row">
39
44
  <Story />
40
45
  <View className="flex-1 items-center justify-center">
41
46
  <Typography variant="body2" color="tertiary">
42
47
  main viewport
43
48
  </Typography>
44
49
  </View>
45
- </View>
50
+ </Surface>
46
51
  ),
47
52
  ],
48
53
  parameters: {
@@ -50,9 +55,11 @@ const meta: Meta<typeof SideNav> = {
50
55
  docs: {
51
56
  description: {
52
57
  component:
53
- '**Organism** (shell region). The persistent 60px left rail switching Live · Review · Plan · ' +
54
- 'Body. Composes [NavItem](?path=/docs/shell-navitem--docs) × the four categories + the ' +
55
- 'shared [icon set](?path=/docs/foundations-icons--docs). Presentational drive it with `activeKey` / ' +
58
+ '**Organism** (shell region). The persistent 60px left rail that switches the main viewport ' +
59
+ "between an app's categories. Composes [NavItem](?path=/docs/shell-navitem--docs) × `items` + the " +
60
+ 'shared [icon set](?path=/docs/foundations-icons--docs). It has no built-in categories: `items` is ' +
61
+ 'required, and each app supplies its own (the workout set is `workoutNavItems`). ' +
62
+ 'Presentational — drive it with `activeKey` / ' +
56
63
  '`onNavigate` / `liveKey`. Active = left accent bar; `liveKey` (when not active) tints that ' +
57
64
  'label a muted green.\n\n' +
58
65
  '**Try it:** stories render in a full-height shell frame — the rail pins to the left edge and ' +
@@ -79,3 +86,27 @@ export const LiveElsewhere: Story = {
79
86
  },
80
87
  },
81
88
  }
89
+
90
+ /** A different app's categories and accent in the same rail (AW-132). */
91
+ export const AnotherApp: Story = {
92
+ args: {
93
+ items: [
94
+ { key: 'notes', label: 'Notes', icon: <LayersIcon size={20} color="currentColor" /> },
95
+ { key: 'graph', label: 'Graph', icon: <BrainIcon size={20} color="currentColor" /> },
96
+ { key: 'agents', label: 'Agents', icon: <BotIcon size={20} color="currentColor" /> },
97
+ ],
98
+ activeKey: 'graph',
99
+ accentClassName: brandPresets.brain.accentClassName,
100
+ accentBarClassName: brandPresets.brain.accentBarClassName,
101
+ },
102
+ parameters: {
103
+ docs: {
104
+ description: {
105
+ story:
106
+ "The brain app's three categories in its own accent. Same rail, same states, different " +
107
+ '`items` and accent. `AppShell` passes the accent down from its `brand`; drive the bare ' +
108
+ 'rail with `accentClassName` / `accentBarClassName`.',
109
+ },
110
+ },
111
+ },
112
+ }
@@ -1,11 +1,19 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
2
  import { render, screen, fireEvent, within } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
- import { SideNav } from './SideNav'
4
+ import { SideNav, type SideNavItem } from './SideNav'
5
+
6
+ // A generic four-category set — the rail has no built-in categories any more.
7
+ const items: SideNavItem[] = [
8
+ { key: 'live', label: 'Live', icon: null },
9
+ { key: 'review', label: 'Review', icon: null },
10
+ { key: 'program', label: 'Plan', icon: null },
11
+ { key: 'body', label: 'Body', icon: null },
12
+ ]
5
13
 
6
14
  describe('SideNav', () => {
7
- it('renders the four default categories as tabs in a tablist', () => {
8
- render(<SideNav activeKey="live" />)
15
+ it('renders the supplied categories as tabs in a tablist', () => {
16
+ render(<SideNav items={items} activeKey="live" />)
9
17
  expect(screen.getByRole('tablist')).toBeInTheDocument()
10
18
  ;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
11
19
  expect(screen.getByRole('tab', { name })).toBeInTheDocument()
@@ -13,7 +21,7 @@ describe('SideNav', () => {
13
21
  })
14
22
 
15
23
  it('shows the accent bar only on the active category', () => {
16
- render(<SideNav activeKey="program" />)
24
+ render(<SideNav items={items} activeKey="program" />)
17
25
  expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
18
26
  expect(
19
27
  within(screen.getByRole('tab', { name: 'Plan' })).getByTestId('nav-item-accent')
@@ -25,13 +33,13 @@ describe('SideNav', () => {
25
33
 
26
34
  it('reports the tapped key via onNavigate', () => {
27
35
  const onNavigate = vi.fn()
28
- render(<SideNav activeKey="live" onNavigate={onNavigate} />)
36
+ render(<SideNav items={items} activeKey="live" onNavigate={onNavigate} />)
29
37
  fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
30
38
  expect(onNavigate).toHaveBeenCalledWith('body')
31
39
  })
32
40
 
33
41
  it('keeps the active item as active even when it is also the liveKey (no double-state)', () => {
34
- render(<SideNav activeKey="live" liveKey="live" />)
42
+ render(<SideNav items={items} activeKey="live" liveKey="live" />)
35
43
  // active wins: exactly one accent bar, on Live (the live cue never applies to the active view)
36
44
  expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
37
45
  expect(
@@ -39,13 +47,13 @@ describe('SideNav', () => {
39
47
  ).toBeInTheDocument()
40
48
  })
41
49
 
42
- it('accepts custom items', () => {
50
+ it('accepts an app-specific item set', () => {
43
51
  render(<SideNav activeKey="a" items={[{ key: 'a', label: 'Alpha', icon: null }]} />)
44
52
  expect(screen.getByRole('tab', { name: 'Alpha' })).toBeInTheDocument()
45
53
  })
46
54
 
47
55
  it('has no accessibility violations', async () => {
48
- const { container } = render(<SideNav activeKey="live" />)
56
+ const { container } = render(<SideNav items={items} activeKey="live" />)
49
57
  expect(await axe(container)).toHaveNoViolations()
50
58
  })
51
59
  })
@@ -1,7 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { View } from 'react-native'
3
3
  import { cn } from '../../utils/cn'
4
- import { ActivityIcon, HistoryIcon, LayersIcon, PersonStandingIcon } from '../icons'
5
4
  import { NavItem } from './NavItem'
6
5
 
7
6
  export interface SideNavItem {
@@ -14,8 +13,8 @@ export interface SideNavItem {
14
13
  }
15
14
 
16
15
  export interface SideNavProps {
17
- /** Category items, top → bottom. Defaults to the four dashboard categories. */
18
- items?: SideNavItem[]
16
+ /** Category items, top → bottom. The app owns its own categories. */
17
+ items: SideNavItem[]
19
18
  /** Key of the active category. */
20
19
  activeKey: string
21
20
  /** Called with the tapped item's key. */
@@ -25,30 +24,29 @@ export interface SideNavProps {
25
24
  * running off its view) → a quiet green cue on that item's label.
26
25
  */
27
26
  liveKey?: string | null
27
+ /** Semantic `text-*` token for the active item. Defaults to the Voltras brand. */
28
+ accentClassName?: string
29
+ /** Semantic `bg-*` token for the active bar. Pair it with `accentClassName`. */
30
+ accentBarClassName?: string
28
31
  className?: string
29
32
  }
30
33
 
31
- /** The four dashboard categories with their locked S2 glyphs (activity · history · layers · figure). */
32
- export const defaultNavItems: SideNavItem[] = [
33
- { key: 'live', label: 'Live', icon: <ActivityIcon size={20} color="currentColor" /> },
34
- { key: 'review', label: 'Review', icon: <HistoryIcon size={20} color="currentColor" /> },
35
- { key: 'program', label: 'Plan', icon: <LayersIcon size={20} color="currentColor" /> },
36
- { key: 'body', label: 'Body', icon: <PersonStandingIcon size={20} color="currentColor" /> },
37
- ]
38
-
39
34
  /**
40
35
  * Shell S2 · SideNav — the persistent 60px left rail that switches the main
41
- * viewport between categories (Live · Review · Program · Body). Presentational:
42
- * it renders `items` and reports taps via `onNavigate`; the app owns routing and
43
- * which key is `live`. Active item shows a left accent bar; `liveKey` (when not
44
- * the active view) tints that item's label a muted green. Fixed 60px at every
45
- * width — labels sit under the glyph, so they never change the rail width.
36
+ * viewport between an app's categories. Presentational: it renders `items` and
37
+ * reports taps via `onNavigate`; the app owns its categories, routing, and which
38
+ * key is `live`. Active item shows a left accent bar in the app's brand accent;
39
+ * `liveKey` (when not the active view) tints that item's label a muted green.
40
+ * Fixed 60px at every width — labels sit under the glyph, so they never change
41
+ * the rail width.
46
42
  */
47
43
  export function SideNav({
48
- items = defaultNavItems,
44
+ items,
49
45
  activeKey,
50
46
  onNavigate,
51
47
  liveKey = null,
48
+ accentClassName,
49
+ accentBarClassName,
52
50
  className,
53
51
  }: SideNavProps) {
54
52
  return (
@@ -66,6 +64,8 @@ export function SideNav({
66
64
  label={item.label}
67
65
  active={item.key === activeKey}
68
66
  live={item.key === liveKey && item.key !== activeKey}
67
+ accentClassName={accentClassName}
68
+ accentBarClassName={accentBarClassName}
69
69
  onPress={() => onNavigate?.(item.key)}
70
70
  />
71
71
  ))}
@@ -1,44 +1,41 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Pill } from '../ui/pill'
2
3
  import { TopBar } from './TopBar'
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
- ]
4
+ import { brandKeys } from './brands'
10
5
 
11
6
  const meta: Meta<typeof TopBar> = {
12
7
  title: 'Shell/TopBar',
13
8
  component: TopBar,
14
- tags: ['autodocs'],
15
- args: { state: 'live', devices: DEVICES },
9
+ tags: ['autodocs', 'status:candidate', '!status:review'],
10
+ args: { brand: 'voltras' },
16
11
  argTypes: {
17
- state: { control: 'select', options: ['live', 'rest', 'idle'] },
18
- devices: { control: 'object' },
12
+ brand: { control: 'select', options: brandKeys },
19
13
  subtitle: { control: 'text' },
20
14
  showSubtitle: { control: 'boolean' },
21
15
  showClock: { control: 'boolean' },
22
16
  time: { control: false },
23
- onSelectDevice: { control: false },
17
+ leading: { control: false },
18
+ trailing: { control: false },
24
19
  },
25
20
  parameters: {
26
21
  layout: 'fullscreen',
27
22
  docs: {
28
23
  description: {
29
24
  component:
30
- '**Organism** — the persistent shell chrome band, and the root of the top-bar family ' +
25
+ '**Organism** — the persistent shell chrome band, generic over the app ' +
31
26
  '(see `shell/README.md` for the full dependency map). Composes ' +
32
27
  '[BrandLockup](?path=/docs/shell-brandlockup--docs) + ' +
33
- '[SessionStatePill](?path=/docs/shell-sessionstatepill--docs) + ' +
34
28
  '[Divider](?path=/docs/components-atoms-divider--docs) (`bg-border-prominent`) + ' +
35
- '[DeviceMenu](?path=/docs/shell-devicemenu--docs) + ' +
36
29
  '[DateTime](?path=/docs/components-molecules-datetime--docs) (`variant="mono"` live clock). ' +
37
30
  'Background = the shared `surfaceGradient.chrome` primitive.\n\n' +
38
- '**Try it:** use the **Controls** to change `state`, edit `devices` (set one to `lost` to see the ' +
39
- 'fault), or toggle `showSubtitle` / `showClock`. **Resize the canvas** to watch the ' +
40
- 'container-responsive collapse (SIZE-D01) subtitle drops < 1024px, clock < 720px. ' +
41
- 'Click the device glyph to open the menu.',
31
+ '**Composition (AW-132).** The bar owns the band, the brand region, the divider ' +
32
+ 'rhythm and the edge-pinned clock. An app supplies its own chrome through ' +
33
+ '`trailing` pass an array and the bar puts its dividers between the items. ' +
34
+ "`leading` replaces the brand region outright. The workout app's cluster lives in " +
35
+ '[WorkoutTopBar](?path=/docs/shell-workout-workouttopbar--docs).\n\n' +
36
+ '**Try it:** switch `brand` in the **Controls**, or toggle `showSubtitle` / ' +
37
+ '`showClock`. **Resize the canvas** to watch the container-responsive collapse ' +
38
+ '(SIZE-D01) — subtitle drops < 1024px, clock < 720px.',
42
39
  },
43
40
  },
44
41
  },
@@ -46,4 +43,32 @@ const meta: Meta<typeof TopBar> = {
46
43
  export default meta
47
44
  type Story = StoryObj<typeof TopBar>
48
45
 
46
+ /** Brand + clock only. No app has supplied any chrome. */
49
47
  export const Default: Story = {}
48
+
49
+ /** Two app-supplied items in `trailing` — the bar adds the dividers and the clock. */
50
+ export const WithAppChrome: Story = {
51
+ args: {
52
+ brand: 'brain',
53
+ trailing: [
54
+ <Pill key="index" tone="success" leading="dot" size="xs">
55
+ indexed
56
+ </Pill>,
57
+ <Pill key="scope" tone="neutral" size="xs">
58
+ 4 vaults
59
+ </Pill>,
60
+ ],
61
+ },
62
+ }
63
+
64
+ /** One item, no array — a single node is a valid `trailing` too. */
65
+ export const SingleTrailingItem: Story = {
66
+ args: {
67
+ brand: 'audiobook',
68
+ trailing: (
69
+ <Pill tone="brand-secondary" size="xs">
70
+ 3 downloading
71
+ </Pill>
72
+ ),
73
+ },
74
+ }
@@ -1,36 +1,58 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
+ import { Text } from 'react-native'
3
4
  import { axe } from 'jest-axe'
4
5
  import { TopBar } from './TopBar'
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
6
 
12
7
  const AT_1612 = new Date(2024, 0, 1, 16, 12) // local 16:12
13
8
 
14
9
  describe('TopBar', () => {
15
- it('renders brand, state and a 24h clock', () => {
16
- render(<TopBar state="live" devices={devices} time={AT_1612} showClock />)
10
+ it('renders the brand and a 24h clock', () => {
11
+ render(<TopBar time={AT_1612} showClock />)
17
12
  expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
18
- expect(screen.getByText('LIVE')).toBeInTheDocument()
19
13
  expect(screen.getByText('16:12')).toBeInTheDocument()
20
14
  })
21
15
 
16
+ it('renders another app brand without knowing anything about it', () => {
17
+ render(<TopBar brand="brain" showClock={false} />)
18
+ expect(screen.getByText('BRAIN')).toBeInTheDocument()
19
+ expect(screen.getByText('/ knowledge')).toBeInTheDocument()
20
+ })
21
+
22
+ it('mounts app chrome from the trailing slot', () => {
23
+ render(
24
+ <TopBar
25
+ showClock={false}
26
+ trailing={[<Text key="a">alpha</Text>, <Text key="b">beta</Text>]}
27
+ />
28
+ )
29
+ expect(screen.getByText('alpha')).toBeInTheDocument()
30
+ expect(screen.getByText('beta')).toBeInTheDocument()
31
+ })
32
+
33
+ it('accepts a single trailing node as well as an array', () => {
34
+ render(<TopBar showClock={false} trailing={<Text>solo</Text>} />)
35
+ expect(screen.getByText('solo')).toBeInTheDocument()
36
+ })
37
+
38
+ it('replaces the brand region when a leading node is given', () => {
39
+ render(<TopBar leading={<Text>my own lockup</Text>} showClock={false} />)
40
+ expect(screen.getByText('my own lockup')).toBeInTheDocument()
41
+ expect(screen.queryByText('VOLTRAS')).not.toBeInTheDocument()
42
+ })
43
+
22
44
  it('hides the clock when showClock is false', () => {
23
- render(<TopBar state="live" devices={devices} time={AT_1612} showClock={false} />)
45
+ render(<TopBar time={AT_1612} showClock={false} />)
24
46
  expect(screen.queryByText('16:12')).not.toBeInTheDocument()
25
47
  })
26
48
 
27
49
  it('hides the subtitle when showSubtitle is false', () => {
28
- render(<TopBar state="rest" devices={devices} showSubtitle={false} />)
50
+ render(<TopBar showSubtitle={false} />)
29
51
  expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
30
52
  })
31
53
 
32
54
  it('has no accessibility violations', async () => {
33
- const { container } = render(<TopBar state="live" devices={devices} time={AT_1612} showClock />)
55
+ const { container } = render(<TopBar time={AT_1612} showClock trailing={<Text>chrome</Text>} />)
34
56
  expect(await axe(container)).toHaveNoViolations()
35
57
  })
36
58
  })