@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,74 +1,164 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
- import { View, Text, Pressable } from 'react-native'
2
+ import type { ReactNode } from 'react'
3
+ import { View, Pressable } from 'react-native'
4
+ import { Typography } from '../Typography'
5
+ import { cn } from '../../../utils/cn'
3
6
  import { roundWeight } from '../../../utils/workout-format'
7
+ import { useSurfaceMode } from '../../ui/surface/SurfaceContext'
4
8
  import { SetsRepsLoad } from './SetsRepsLoad'
5
9
  import { TempoDisplay } from './TempoDisplay'
6
10
  import { ExerciseIndicator, type ExerciseIndicatorKind } from './ExerciseIndicator'
11
+ import { exerciseLiveColor } from './exerciseRowState'
7
12
 
8
- export interface ExerciseHeadingProps {
13
+ /** Dim depth for a not-yet-reached exercise, when the block dims itself. */
14
+ const DIMMED_OPACITY = 0.55
15
+
16
+ /** `stacked` puts the prescription on its own line under the name; `inline` keeps one row. */
17
+ export type ExerciseHeadingLayout = 'stacked' | 'inline'
18
+
19
+ interface ExerciseHeadingBaseProps {
9
20
  name: string
10
- /** Prescription line values (sets × reps @ load). */
11
- sets: number
12
- reps: number | string
13
- /** Load value; a string (e.g. "—") passes through verbatim for an unset/discovery load. */
14
- load: number | string
15
21
  unit?: 'lbs' | 'kg'
16
- /** Tempo tuple [eccentric, pauseBottom, concentric, pauseTop]; hidden when absent. */
22
+ /** Tempo tuple [eccentric, pauseBottom, concentric, pauseTop]. `stacked` layout only. */
17
23
  tempo?: [number, number, number, number]
18
24
  /** Small PR / issue / info chip in the title row. */
19
25
  indicator?: ExerciseIndicatorKind
20
26
  /** Dim the block as a not-yet-reached exercise (standalone use). */
21
27
  dimmed?: boolean
28
+ /** Row shape. Default `stacked`. */
29
+ layout?: ExerciseHeadingLayout
30
+ /**
31
+ * Render the structured `sets × reps @ load` prescription in one dimmer, regular
32
+ * tone instead of the rail's bright/bold value cells. `stacked` (rail) never sets
33
+ * this; `inline` (compact, upcoming) always does.
34
+ */
35
+ mutedPrescription?: boolean
36
+ /** Trailing right-edge caption (a previous best). `inline` layout only. */
37
+ previousBest?: string
38
+ /** This exercise is being performed right now — the name takes the live tone, never animated. */
39
+ isLive?: boolean
40
+ /**
41
+ * Who owns the press. `self` (default) makes the name row the button. `ancestor`
42
+ * renders it as a plain row, for a wrapper that presses as a whole — legal only in
43
+ * `inline` layout, since `stacked`'s TempoDisplay sibling is itself a Pressable.
44
+ */
45
+ pressTarget?: 'self' | 'ancestor'
22
46
  /** Press target for the name row (expand / select). */
23
47
  onPress?: () => void
24
- }
25
-
26
- function headingLabel(
27
- name: string,
28
- sets: number,
29
- reps: number | string,
30
- load: number | string,
31
- unit: string
32
- ) {
33
- const loadLabel = typeof load === 'number' ? roundWeight(load) : load
34
- return `${name}, ${sets}×${reps} @ ${loadLabel} ${unit}`
48
+ onPressIn?: () => void
49
+ onPressOut?: () => void
50
+ /** Web-only; on a touch surface these never fire and the row has no hover state. */
51
+ onHoverIn?: () => void
52
+ onHoverOut?: () => void
35
53
  }
36
54
 
37
55
  /**
38
- * The exercise-heading info block (no strip): the name + {@link ExerciseIndicator}
39
- * title row over a tight {@link SetsRepsLoad} prescription line beside the real
40
- * {@link TempoDisplay} (`showLabel={false}`). The name row is the sole press
41
- * target — TempoDisplay is a sibling, never a descendant, since it is itself a
42
- * Pressable (nesting would be an a11y nested-interactive violation).
56
+ * The prescription is EITHER the structured `sets × reps @ load` triple, OR a
57
+ * free-text line for an exercise whose numbers aren't loaded yet, OR absent.
58
+ * Mixing the two is a type error rather than a silently half-rendered line.
43
59
  */
44
- export function ExerciseHeading({
60
+ type ExerciseHeadingPrescription =
61
+ | { sets: number; reps: number | string; load: number | string; prescription?: never }
62
+ | { prescription: string; sets?: never; reps?: never; load?: never }
63
+ | { sets?: never; reps?: never; load?: never; prescription?: never }
64
+
65
+ export type ExerciseHeadingProps = ExerciseHeadingBaseProps & ExerciseHeadingPrescription
66
+
67
+ function prescriptionText(p: ExerciseHeadingProps, unit: string): string | undefined {
68
+ if (p.prescription !== undefined) return p.prescription
69
+ if (p.sets === undefined) return undefined
70
+ const loadLabel = typeof p.load === 'number' ? roundWeight(p.load) : p.load
71
+ return `${p.sets}×${p.reps} @ ${loadLabel} ${unit}`
72
+ }
73
+
74
+ /** The row's accessible name. Exported so a wrapper that owns the press can reuse it. */
75
+ export function exerciseHeadingLabel(p: ExerciseHeadingProps, unit: string = p.unit ?? 'lbs') {
76
+ const prescription = prescriptionText(p, unit)
77
+ return prescription ? `${p.name}, ${prescription}` : p.name
78
+ }
79
+
80
+ function pressProps(p: ExerciseHeadingProps, unit: string) {
81
+ return {
82
+ onPress: p.onPress,
83
+ onPressIn: p.onPressIn,
84
+ onPressOut: p.onPressOut,
85
+ onHoverIn: p.onHoverIn,
86
+ onHoverOut: p.onHoverOut,
87
+ accessibilityRole: 'button' as const,
88
+ accessibilityLabel: exerciseHeadingLabel(p, unit),
89
+ }
90
+ }
91
+
92
+ interface NameStyle {
93
+ color: string
94
+ }
95
+
96
+ function HeadingName({
45
97
  name,
46
- sets,
47
- reps,
48
- load,
49
- unit = 'lbs',
50
- tempo,
51
- indicator,
52
- dimmed,
53
- onPress,
54
- }: ExerciseHeadingProps) {
98
+ nameStyle,
99
+ className,
100
+ }: {
101
+ name: string
102
+ nameStyle?: NameStyle
103
+ className?: string
104
+ }) {
105
+ return (
106
+ // The row is a button label, not a heading, so `boldLabel` carries the weight and
107
+ // no header role has to be undone. Size and the Space Grotesk face are pinned.
108
+ <Typography
109
+ variant="boldLabel"
110
+ color={nameStyle ? 'inherit' : 'primary'}
111
+ style={nameStyle}
112
+ className={cn('font-heading text-sm leading-[normal]', className)}
113
+ testID="exercise-card-name"
114
+ >
115
+ {name}
116
+ </Typography>
117
+ )
118
+ }
119
+
120
+ function prescriptionNode(p: ExerciseHeadingProps, unit: string): ReactNode {
121
+ if (p.prescription !== undefined) {
122
+ return (
123
+ <Typography
124
+ variant="caption"
125
+ color="secondary"
126
+ maxLines={1}
127
+ className="font-sans shrink leading-[normal]"
128
+ testID="exercise-card-prescription"
129
+ >
130
+ {p.prescription}
131
+ </Typography>
132
+ )
133
+ }
134
+ if (p.sets === undefined) return null
55
135
  return (
56
- <View style={{ opacity: dimmed ? 0.55 : 1 }} testID="exercise-heading">
136
+ <SetsRepsLoad
137
+ sets={p.sets}
138
+ reps={p.reps}
139
+ load={p.load}
140
+ unit={unit}
141
+ muted={p.mutedPrescription}
142
+ />
143
+ )
144
+ }
145
+
146
+ interface LayoutProps {
147
+ heading: ExerciseHeadingProps
148
+ nameStyle?: NameStyle
149
+ }
150
+
151
+ function StackedHeading({ heading, nameStyle }: LayoutProps) {
152
+ const { unit = 'lbs', tempo, indicator } = heading
153
+ return (
154
+ <>
57
155
  <Pressable
58
- onPress={onPress}
59
- accessibilityRole="button"
60
- accessibilityLabel={headingLabel(name, sets, reps, load, unit)}
156
+ {...pressProps(heading, unit)}
61
157
  className="flex-row items-center"
62
158
  style={{ gap: 8 }}
63
159
  testID="exercise-card-header"
64
160
  >
65
- <Text
66
- className="text-text-primary"
67
- style={{ fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: '700' }}
68
- testID="exercise-card-name"
69
- >
70
- {name}
71
- </Text>
161
+ <HeadingName name={heading.name} nameStyle={nameStyle} />
72
162
  <View className="flex-1" />
73
163
  {indicator && <ExerciseIndicator kind={indicator} />}
74
164
  </Pressable>
@@ -78,10 +168,68 @@ export function ExerciseHeading({
78
168
  style={{ gap: 8, marginTop: 1 }}
79
169
  testID="exercise-card-summary"
80
170
  >
81
- <SetsRepsLoad sets={sets} reps={reps} load={load} unit={unit} />
171
+ {prescriptionNode(heading, unit)}
82
172
  <View className="flex-1" />
83
173
  {tempo && <TempoDisplay tempo={tempo} size="sm" showLabel={false} showInfo={false} />}
84
174
  </View>
175
+ </>
176
+ )
177
+ }
178
+
179
+ function InlineHeading({ heading, nameStyle }: LayoutProps) {
180
+ const { unit = 'lbs', indicator, previousBest } = heading
181
+ const Row = heading.pressTarget === 'ancestor' ? View : Pressable
182
+ return (
183
+ <Row
184
+ {...(heading.pressTarget === 'ancestor' ? {} : pressProps(heading, unit))}
185
+ className="flex-row items-center"
186
+ style={{ gap: 8 }}
187
+ testID="exercise-card-header"
188
+ >
189
+ {/* Name never truncates (no numberOfLines); prescription + previousBest ellipsize first. */}
190
+ <HeadingName name={heading.name} nameStyle={nameStyle} className="shrink-0" />
191
+ <View className="shrink flex-row items-center" testID="exercise-card-summary">
192
+ {prescriptionNode(heading, unit)}
193
+ </View>
194
+ <View className="flex-1" style={{ minWidth: 8 }} />
195
+ {indicator && <ExerciseIndicator kind={indicator} />}
196
+ {previousBest && (
197
+ <Typography
198
+ variant="caption"
199
+ color="tertiary"
200
+ maxLines={1}
201
+ className="font-sans shrink text-2xs leading-[normal]"
202
+ testID="exercise-card-previous-best"
203
+ >
204
+ {previousBest}
205
+ </Typography>
206
+ )}
207
+ </Row>
208
+ )
209
+ }
210
+
211
+ /**
212
+ * The exercise-heading info block (no strip): the name + {@link ExerciseIndicator}
213
+ * title row, its prescription line, and — `inline` only — a trailing previous-best
214
+ * caption. The name row is the sole press target: TempoDisplay and the previous-best
215
+ * caption are siblings of it, never descendants, since TempoDisplay is itself a
216
+ * Pressable (nesting would be an a11y nested-interactive violation).
217
+ *
218
+ * `layout` is the one axis that separates the session-rail row (`stacked`) from the
219
+ * collapsed and upcoming card rows (`inline`) — same parts, same vocabulary, one row
220
+ * instead of two. `inline` drops the tempo: it has no room for a second metric lockup.
221
+ */
222
+ export function ExerciseHeading(props: ExerciseHeadingProps) {
223
+ const mode = useSurfaceMode()
224
+ const nameStyle = props.isLive ? { color: exerciseLiveColor(mode) } : undefined
225
+
226
+ return (
227
+ <View style={{ opacity: props.dimmed ? DIMMED_OPACITY : 1 }} testID="exercise-heading">
228
+ {props.layout === 'inline' ? (
229
+ <InlineHeading heading={props} nameStyle={nameStyle} />
230
+ ) : (
231
+ <StackedHeading heading={props} nameStyle={nameStyle} />
232
+ )}
85
233
  </View>
86
234
  )
87
235
  }
@@ -6,6 +6,8 @@ import {
6
6
  INDICATOR_PRECEDENCE,
7
7
  type ExerciseIndicatorKind,
8
8
  } from './ExerciseIndicator'
9
+ import { Surface } from '../../ui/surface'
10
+ import { greyRamp } from '../../../theme/tokens/primitives'
9
11
 
10
12
  /**
11
13
  * `ExerciseIndicator` — a small circular OUTLINED chip in an exercise heading's
@@ -15,7 +17,7 @@ import {
15
17
  * picks the highest-precedence active signal.
16
18
  */
17
19
  const meta: Meta<typeof ExerciseIndicator> = {
18
- title: 'Workout/ExerciseIndicator',
20
+ title: 'Custom/Workout/ExerciseIndicator',
19
21
  component: ExerciseIndicator,
20
22
  tags: ['autodocs'],
21
23
  parameters: {
@@ -26,7 +28,8 @@ const meta: Meta<typeof ExerciseIndicator> = {
26
28
  'precedence-ranked kinds (imbalance › overshoot › velocity-loss › missed-reps › pr › ' +
27
29
  'info) over four status-token tier colors; glyph = signal, color = severity tier. ' +
28
30
  '`resolveIndicator()` selects the single chip to show. Used-by ↑ ' +
29
- '[ExerciseHeading](?path=/docs/workout-exerciseheading--docs).',
31
+ '[ExerciseHeading](?path=/docs/custom-workout-exerciseheading--docs).' +
32
+ ' Composes **Icons**.',
30
33
  },
31
34
  },
32
35
  },
@@ -36,9 +39,9 @@ const meta: Meta<typeof ExerciseIndicator> = {
36
39
  },
37
40
  decorators: [
38
41
  (Story) => (
39
- <View style={{ padding: 24, backgroundColor: '#131313' }}>
42
+ <Surface level="base" style={{ padding: 24 }}>
40
43
  <Story />
41
- </View>
44
+ </Surface>
42
45
  ),
43
46
  ],
44
47
  }
@@ -68,9 +71,9 @@ export const AllKinds: Story = {
68
71
  <View style={{ gap: 12 }}>
69
72
  {INDICATOR_PRECEDENCE.map((kind, i) => (
70
73
  <View key={kind} style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
71
- <Text style={{ color: '#8A8A8A', fontSize: 11, width: 14 }}>{i + 1}</Text>
74
+ <Text style={{ color: greyRamp[500], fontSize: 11, width: 14 }}>{i + 1}</Text>
72
75
  <ExerciseIndicator kind={kind} />
73
- <Text style={{ color: '#CFCFCF', fontSize: 12 }}>{KIND_LABELS[kind]}</Text>
76
+ <Text style={{ color: greyRamp[200], fontSize: 12 }}>{KIND_LABELS[kind]}</Text>
74
77
  </View>
75
78
  ))}
76
79
  </View>
@@ -95,12 +98,12 @@ export const ResolvePrecedence: Story = {
95
98
  const winner = resolveIndicator(candidates)
96
99
  return (
97
100
  <View key={i} style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
98
- <Text style={{ color: '#8A8A8A', fontSize: 12, width: 200 }}>
101
+ <Text style={{ color: greyRamp[500], fontSize: 12, width: 200 }}>
99
102
  {candidates.join(', ')}
100
103
  </Text>
101
- <Text style={{ color: '#8A8A8A', fontSize: 12 }}>→</Text>
104
+ <Text style={{ color: greyRamp[500], fontSize: 12 }}>→</Text>
102
105
  {winner ? <ExerciseIndicator kind={winner} /> : null}
103
- <Text style={{ color: '#CFCFCF', fontSize: 12 }}>{winner}</Text>
106
+ <Text style={{ color: greyRamp[200], fontSize: 12 }}>{winner}</Text>
104
107
  </View>
105
108
  )
106
109
  })}
@@ -7,18 +7,30 @@ import {
7
7
  INDICATOR_PRECEDENCE,
8
8
  type ExerciseIndicatorKind,
9
9
  } from './ExerciseIndicator'
10
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
10
11
 
11
- // tier color = titan status token, applied as the glyph's SVG stroke (literal hex):
12
- // danger=status-error, warning=status-warning, success=status-success, info=status-info.
13
- const kinds: { kind: ExerciseIndicatorKind; label: string; tier: string }[] = [
14
- { kind: 'imbalance', label: 'Left/right imbalance', tier: '#D14343' },
15
- { kind: 'overshoot', label: 'Load overshoot', tier: '#D14343' },
16
- { kind: 'velocity-loss', label: 'Velocity loss', tier: '#F9B415' },
17
- { kind: 'missed-reps', label: 'Missed reps', tier: '#F9B415' },
18
- { kind: 'pr', label: 'Personal record', tier: '#2ED573' },
19
- { kind: 'info', label: 'More info', tier: '#2196F3' },
12
+ type StatusToken = 'status-error' | 'status-warning' | 'status-success' | 'status-info'
13
+
14
+ // tier color = titan status token, applied as the glyph's SVG stroke. The component
15
+ // resolves it through `resolveColor`, so on web the stroke is the CSS var and the
16
+ // theme drives the value; the dark hex is asserted separately below.
17
+ const kinds: { kind: ExerciseIndicatorKind; label: string; tier: StatusToken }[] = [
18
+ { kind: 'imbalance', label: 'Left/right imbalance', tier: 'status-error' },
19
+ { kind: 'overshoot', label: 'Load overshoot', tier: 'status-error' },
20
+ { kind: 'velocity-loss', label: 'Velocity loss', tier: 'status-warning' },
21
+ { kind: 'missed-reps', label: 'Missed reps', tier: 'status-warning' },
22
+ { kind: 'pr', label: 'Personal record', tier: 'status-success' },
23
+ { kind: 'info', label: 'More info', tier: 'status-info' },
20
24
  ]
21
25
 
26
+ // The dark-mode values the tier tokens must keep resolving to (the pre-port literals).
27
+ const DARK_TIER: Record<StatusToken, string> = {
28
+ 'status-error': '#D14343',
29
+ 'status-warning': '#F9B415',
30
+ 'status-success': '#2ED573',
31
+ 'status-info': '#2196F3',
32
+ }
33
+
22
34
  describe('ExerciseIndicator', () => {
23
35
  it.each(kinds)('renders the $kind chip glyph and accessible label', ({ kind, label }) => {
24
36
  const { container } = render(<ExerciseIndicator kind={kind} />)
@@ -27,9 +39,13 @@ describe('ExerciseIndicator', () => {
27
39
  expect(screen.getByLabelText(label)).toBeInTheDocument()
28
40
  })
29
41
 
30
- it.each(kinds)('tints the $kind glyph with its tier color', ({ kind, tier }) => {
42
+ it.each(kinds)('tints the $kind glyph with its tier token', ({ kind, tier }) => {
31
43
  const { container } = render(<ExerciseIndicator kind={kind} />)
32
- expect(container.querySelector('svg')!.getAttribute('stroke')).toBe(tier)
44
+ expect(container.querySelector('svg')!.getAttribute('stroke')).toBe(`var(--color-${tier})`)
45
+ })
46
+
47
+ it.each(kinds)('keeps the $kind tier on its dark-mode value', ({ tier }) => {
48
+ expect(getSemanticColors('dark')[tier]).toBe(DARK_TIER[tier])
33
49
  })
34
50
 
35
51
  it('renders a decorative (aria-hidden) glyph so the chip owns the label', () => {
@@ -1,7 +1,7 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import type { ComponentType } from 'react'
3
3
  import { View, Pressable, type ViewProps } from 'react-native'
4
- import { getSemanticColors } from '../../../theme/tokens/semantic'
4
+ import { resolveColor } from '../../../theme/resolve-color'
5
5
  import {
6
6
  ScaleIcon,
7
7
  AlertTriangleIcon,
@@ -12,10 +12,6 @@ import {
12
12
  type IconProps,
13
13
  } from '../../icons'
14
14
 
15
- // Literal-hex status pins (getSemanticColors, not resolveColor) — `resolveColor()` yields a
16
- // `var(...)` under the RNW vitest alias, which breaks `toHaveStyle` in tests.
17
- const t = getSemanticColors('dark')
18
-
19
15
  /**
20
16
  * The LOCKED 6-kind indicator taxonomy (TD-03.51). One precedence-ranked slot is
21
17
  * ever shown per exercise, resolved by {@link resolveIndicator}. Glyph = the
@@ -31,21 +27,23 @@ export type ExerciseIndicatorKind =
31
27
  | 'pr'
32
28
  | 'info'
33
29
 
30
+ type StatusToken = 'status-error' | 'status-warning' | 'status-success' | 'status-info'
31
+
34
32
  interface IndicatorConfig {
35
33
  Icon: ComponentType<IconProps>
36
- /** Tier colora titan status semantic token (literal hex on `t`). */
37
- color: string
34
+ /** Tier tokenresolved per theme by {@link resolveColor} at render. */
35
+ token: StatusToken
38
36
  /** Accessible description (also the intended tooltip subject). */
39
37
  label: string
40
38
  }
41
39
 
42
40
  const INDICATOR_CONFIG: Record<ExerciseIndicatorKind, IndicatorConfig> = {
43
- imbalance: { Icon: ScaleIcon, color: t['status-error'], label: 'Left/right imbalance' },
44
- overshoot: { Icon: AlertTriangleIcon, color: t['status-error'], label: 'Load overshoot' },
45
- 'velocity-loss': { Icon: TrendingDownIcon, color: t['status-warning'], label: 'Velocity loss' },
46
- 'missed-reps': { Icon: CircleSlashIcon, color: t['status-warning'], label: 'Missed reps' },
47
- pr: { Icon: AwardIcon, color: t['status-success'], label: 'Personal record' },
48
- info: { Icon: InfoIcon, color: t['status-info'], label: 'More info' },
41
+ imbalance: { Icon: ScaleIcon, token: 'status-error', label: 'Left/right imbalance' },
42
+ overshoot: { Icon: AlertTriangleIcon, token: 'status-error', label: 'Load overshoot' },
43
+ 'velocity-loss': { Icon: TrendingDownIcon, token: 'status-warning', label: 'Velocity loss' },
44
+ 'missed-reps': { Icon: CircleSlashIcon, token: 'status-warning', label: 'Missed reps' },
45
+ pr: { Icon: AwardIcon, token: 'status-success', label: 'Personal record' },
46
+ info: { Icon: InfoIcon, token: 'status-info', label: 'More info' },
49
47
  }
50
48
 
51
49
  /**
@@ -68,7 +66,7 @@ export const INDICATOR_PRECEDENCE: readonly ExerciseIndicatorKind[] = [
68
66
  * duplicates in `candidates` don't matter — {@link INDICATOR_PRECEDENCE} decides.
69
67
  */
70
68
  export function resolveIndicator(
71
- candidates: ExerciseIndicatorKind[],
69
+ candidates: ExerciseIndicatorKind[]
72
70
  ): ExerciseIndicatorKind | undefined {
73
71
  return INDICATOR_PRECEDENCE.find((kind) => candidates.includes(kind))
74
72
  }
@@ -86,7 +84,8 @@ export interface ExerciseIndicatorProps extends Omit<ViewProps, 'children'> {
86
84
  * by the consumer.
87
85
  */
88
86
  export function ExerciseIndicator({ kind, onPress, className, ...props }: ExerciseIndicatorProps) {
89
- const { Icon, color, label } = INDICATOR_CONFIG[kind]
87
+ const { Icon, token, label } = INDICATOR_CONFIG[kind]
88
+ const color = resolveColor(token)
90
89
 
91
90
  const chip = (
92
91
  <View
@@ -1,10 +1,10 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite'
3
- import { View } from 'react-native'
4
3
  import { FatigueMeter } from './FatigueMeter'
4
+ import { Surface } from '../../ui/surface'
5
5
 
6
6
  const meta: Meta<typeof FatigueMeter> = {
7
- title: 'Workout/DataViz/FatigueMeter',
7
+ title: 'Custom/Workout/DataViz/FatigueMeter',
8
8
  component: FatigueMeter,
9
9
  tags: ['autodocs'],
10
10
  argTypes: {
@@ -16,16 +16,16 @@ const meta: Meta<typeof FatigueMeter> = {
16
16
  component:
17
17
  '**Component.** The winning fatigue-visual lineage: a sliding needle over a fixed ' +
18
18
  'green→gold→orange→red velocity-loss gradient with VL10/VL20/VL30/stop markers. ' +
19
- 'Composes the [ZoneTrack](?path=/docs/workout-dataviz-zonetrack--docs) primitive. ' +
19
+ 'Composes the [ZoneTrack](?path=/docs/custom-workout-dataviz-zonetrack--docs) primitive. ' +
20
20
  'Prop-driven: `value` plus overridable `thresholds` / `max` / `zoneColors` / `labels`.',
21
21
  },
22
22
  },
23
23
  },
24
24
  decorators: [
25
25
  (Story) => (
26
- <View style={{ width: 360, padding: 20, backgroundColor: '#131313' }}>
26
+ <Surface level="base" style={{ width: 360, padding: 20 }}>
27
27
  <Story />
28
- </View>
28
+ </Surface>
29
29
  ),
30
30
  ],
31
31
  }
@@ -1,10 +1,9 @@
1
- import React from 'react'
2
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
3
- import { View } from 'react-native'
4
2
  import { InputBar } from './InputBar'
3
+ import { Surface } from '../../ui/surface'
5
4
 
6
5
  const meta: Meta<typeof InputBar> = {
7
- title: 'Workout/InputBar',
6
+ title: 'Custom/Workout/InputBar',
8
7
  component: InputBar,
9
8
  tags: ['autodocs'],
10
9
  argTypes: {
@@ -24,9 +23,9 @@ const meta: Meta<typeof InputBar> = {
24
23
  },
25
24
  decorators: [
26
25
  (Story) => (
27
- <View style={{ width: 400, backgroundColor: '#111111' }}>
26
+ <Surface level="base" style={{ width: 400 }}>
28
27
  <Story />
29
- </View>
28
+ </Surface>
30
29
  ),
31
30
  ],
32
31
  }
@@ -18,7 +18,9 @@ export interface InputBarProps {
18
18
  visible: boolean
19
19
  }
20
20
 
21
- const BRAND_PRIMARY = getSemanticColors('dark')['brand-primary']
21
+ const t = getSemanticColors('dark')
22
+ const BRAND_PRIMARY = t['brand-primary']
23
+ const ON_BRAND_PRIMARY = t['on-brand-primary']
22
24
  const INPUT_CLASSNAME = 'bg-surface-raised border-hairline-strong text-text-primary'
23
25
 
24
26
  const inputStyle = {
@@ -151,7 +153,7 @@ export function InputBar({
151
153
  fontSize: 13,
152
154
  fontWeight: '700',
153
155
  fontFamily: 'Inter, sans-serif',
154
- color: '#FFFFFF',
156
+ color: ON_BRAND_PRIMARY,
155
157
  }}
156
158
  >
157
159
  Record
@@ -3,7 +3,7 @@ import { View } from 'react-native'
3
3
  import { IntensityBar } from './IntensityBar'
4
4
 
5
5
  const meta: Meta<typeof IntensityBar> = {
6
- title: 'Workout/DataViz/IntensityBar',
6
+ title: 'Custom/Workout/DataViz/IntensityBar',
7
7
  component: IntensityBar,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
@@ -2,8 +2,12 @@ import { describe, it, expect } from 'vitest'
2
2
  import { render, screen } from '@testing-library/react'
3
3
  import { axe } from 'jest-axe'
4
4
  import { IntensityBar } from './IntensityBar'
5
+ import { getGlowShadow } from '../../../theme/elevation'
6
+ import { getSemanticColors } from '../../../theme/tokens/semantic'
5
7
 
6
- const AT_TARGET_GLOW = '0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)'
8
+ const AT_TARGET_GLOW = (
9
+ getGlowShadow(getSemanticColors('dark')['status-info'], 'subtle') as { boxShadow: string }
10
+ ).boxShadow
7
11
 
8
12
  describe('IntensityBar', () => {
9
13
  it('renders the bar', () => {
@@ -9,7 +9,9 @@ import {
9
9
  type DimensionValue,
10
10
  } from 'react-native'
11
11
  import { getSemanticColors } from '../../../theme/tokens/semantic'
12
+ import { getGlowShadow } from '../../../theme/elevation'
12
13
  import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
14
+ import { alpha } from '../../../utils/colors'
13
15
 
14
16
  const t = getSemanticColors('dark')
15
17
 
@@ -31,10 +33,13 @@ export interface IntensityBarProps extends ViewProps {
31
33
  className?: string
32
34
  }
33
35
 
36
+ // VW-82: neither value is on a ramp (nearest surface-raised #31302F / greyRamp[600]
37
+ // #72716F). Left raw pending the proposed roles — snapping them is a visual change.
34
38
  const TRACK_BG = '#333333'
35
39
  const LABEL_COLOR = '#6B7280'
36
- const TARGET_LINE_COLOR = 'rgba(33, 150, 243, 0.5)'
37
- const AT_TARGET_GLOW = '0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)'
40
+ const TARGET_LINE_COLOR = alpha(t['status-info'], 0.5)
41
+ /** At-target is EMPHASIS, not depth: an info-toned glow through the shared builder. */
42
+ const AT_TARGET_GLOW = getGlowShadow(t['status-info'], 'subtle')
38
43
 
39
44
  // Zone colors graded by TRUE percentage (level * 100).
40
45
  const ZONE = {
@@ -178,7 +183,7 @@ export function IntensityBar({
178
183
  borderRadius: 3,
179
184
  backgroundColor: zone.color,
180
185
  zIndex: 2,
181
- ...(atTarget ? { boxShadow: AT_TARGET_GLOW } : null),
186
+ ...(atTarget ? AT_TARGET_GLOW : null),
182
187
  }}
183
188
  testID="intensity-fill"
184
189
  />
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { View, Text } from 'react-native'
3
3
  import { LiveAuraFrame } from './LiveAuraFrame'
4
4
  import { getSemanticColors } from '../../../theme/tokens/semantic'
5
+ import { Surface } from '../../ui/surface'
5
6
 
6
7
  const t = getSemanticColors('dark')
7
8
 
@@ -74,7 +75,7 @@ function LiveHero({ caption }: { caption: string }) {
74
75
  const PANEL = { width: 360, height: 620, borderRadius: 24, overflow: 'hidden' as const }
75
76
 
76
77
  const meta: Meta<typeof LiveAuraFrame> = {
77
- title: 'Workout/LiveAuraFrame',
78
+ title: 'Custom/Workout/LiveAuraFrame',
78
79
  component: LiveAuraFrame,
79
80
  tags: ['autodocs'],
80
81
  parameters: {
@@ -100,17 +101,17 @@ const meta: Meta<typeof LiveAuraFrame> = {
100
101
  },
101
102
  decorators: [
102
103
  (Story) => (
103
- <View
104
+ <Surface
105
+ level="base"
104
106
  style={{
105
107
  minHeight: 700,
106
- backgroundColor: '#070707',
107
108
  alignItems: 'center',
108
109
  justifyContent: 'center',
109
110
  padding: 32,
110
111
  }}
111
112
  >
112
113
  <Story />
113
- </View>
114
+ </Surface>
114
115
  ),
115
116
  ],
116
117
  render: (args) => (