@titan-design/react-ui 0.12.1 → 0.13.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 (509) hide show
  1. package/README.md +46 -47
  2. package/dist/bodymap.js +768 -701
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +770 -703
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1319 -216
  7. package/dist/index.d.ts +1319 -216
  8. package/dist/index.js +5996 -4498
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +5773 -4357
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-CvsFqNtx.d.mts → pages-CpW3OB3g.d.mts} +21 -3
  13. package/dist/{pages-B1hqnGIC.d.ts → pages-_3Ajpiqu.d.ts} +21 -3
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1435 -1428
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1443 -1436
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +195 -134
  21. package/dist/theme/index.d.ts +195 -134
  22. package/dist/theme/index.js +215 -300
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +202 -296
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +62 -5
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +62 -5
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +73 -5
  31. package/dist/theme/tokens.d.ts +73 -5
  32. package/dist/theme/tokens.js +55 -7
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +55 -8
  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/package.json +1 -1
  40. package/src/arch/arch-graph.freshness.test.ts +52 -0
  41. package/src/arch/arch-graph.json +5021 -1617
  42. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  43. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  44. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  45. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  46. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  48. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  49. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  51. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  52. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  54. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  55. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  57. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  58. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  60. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  61. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +181 -0
  63. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  64. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  66. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  67. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  69. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  70. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  71. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  73. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  74. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  76. package/src/components/custom/ActiveWork/README.md +305 -0
  77. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  78. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  80. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  81. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  82. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  83. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  84. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  86. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  87. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  88. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  90. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  91. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  92. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  93. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  94. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  95. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  96. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  97. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  98. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  99. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  100. package/src/components/custom/ActiveWork/index.ts +19 -0
  101. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  102. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  103. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  104. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  105. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +4 -3
  106. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  107. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  108. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  109. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  110. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  111. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  112. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  113. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  116. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  117. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  119. package/src/components/custom/Fatigue/README.md +40 -3
  120. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  121. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  122. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  123. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  124. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  125. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  126. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  127. package/src/components/custom/Fatigue/index.ts +17 -5
  128. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  129. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  130. package/src/components/custom/Gauge/Gauge.stories.tsx +2 -2
  131. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  132. package/src/components/custom/Gauge/Gauge.tsx +4 -1
  133. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  134. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  135. package/src/components/custom/Metric/Metric.tsx +5 -20
  136. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  137. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  138. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  139. package/src/components/custom/Prose/README.md +58 -0
  140. package/src/components/custom/Prose/index.ts +1 -0
  141. package/src/components/custom/Scatter/Scatter.stories.tsx +46 -8
  142. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  143. package/src/components/custom/Scatter/Scatter.tsx +116 -14
  144. package/src/components/custom/Sidebar/Sidebar.stories.tsx +40 -7
  145. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  146. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  147. package/src/components/custom/Table/Table.stories.tsx +98 -15
  148. package/src/components/custom/Table/Table.test.tsx +273 -86
  149. package/src/components/custom/Table/Table.tsx +159 -67
  150. package/src/components/custom/Table/column-fit.test.ts +75 -0
  151. package/src/components/custom/Table/column-fit.ts +116 -0
  152. package/src/components/custom/Table/index.ts +9 -0
  153. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  154. package/src/components/custom/Treemap/Treemap.stories.tsx +2 -2
  155. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  156. package/src/components/custom/Treemap/Treemap.tsx +7 -2
  157. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  158. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  159. package/src/components/custom/Typography/Typography.tsx +6 -0
  160. package/src/components/custom/Typography/index.ts +1 -8
  161. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +9 -1
  162. package/src/components/custom/Workout/BaseBadge.stories.tsx +19 -7
  163. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  164. package/src/components/custom/Workout/BaseBadge.tsx +3 -0
  165. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  166. package/src/components/custom/Workout/BodyMap.tsx +3 -1
  167. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +16 -4
  168. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  169. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +45 -9
  170. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +10 -8
  171. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  172. package/src/components/custom/Workout/CapacityBandChart.tsx +139 -108
  173. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  174. package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
  175. package/src/components/custom/Workout/DeviationBar.tsx +21 -16
  176. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +182 -0
  177. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +3 -2
  178. package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -7
  179. package/src/components/custom/Workout/ExerciseCard.test.tsx +13 -3
  180. package/src/components/custom/Workout/ExerciseCard.tsx +42 -51
  181. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +7 -7
  182. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  183. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  184. package/src/components/custom/Workout/ExerciseHeading.tsx +8 -5
  185. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -4
  186. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  187. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  188. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  189. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  190. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  191. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  192. package/src/components/custom/Workout/IntensityBar.tsx +4 -2
  193. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  194. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  195. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  196. package/src/components/custom/Workout/MesoCard.tsx +12 -9
  197. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  198. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  199. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  200. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  201. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  202. package/src/components/custom/Workout/MesoStatusCard.tsx +9 -23
  203. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  204. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  205. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  206. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  207. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  208. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  209. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  210. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  211. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  212. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  213. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +4 -1
  214. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  215. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  216. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  217. package/src/components/custom/Workout/README.md +11 -6
  218. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  219. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  220. package/src/components/custom/Workout/ReadinessCheck.tsx +19 -3
  221. package/src/components/custom/Workout/RestTimer.stories.tsx +3 -2
  222. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  223. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  224. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  225. package/src/components/custom/Workout/SegmentedBar.stories.tsx +5 -5
  226. package/src/components/custom/Workout/SegmentedBar.tsx +6 -2
  227. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  228. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  229. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  230. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  231. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  232. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  233. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  234. package/src/components/custom/Workout/SetBar.tsx +38 -12
  235. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  236. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  237. package/src/components/custom/Workout/SetRow.tsx +61 -23
  238. package/src/components/custom/Workout/SetStrip.stories.tsx +60 -5
  239. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  240. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  241. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  242. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  243. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  244. package/src/components/custom/Workout/SetsRepsLoad.tsx +3 -5
  245. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  246. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  247. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  248. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  249. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  250. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  251. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  252. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  253. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  254. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +9 -3
  255. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  256. package/src/components/custom/Workout/StrengthTrendChart.tsx +31 -30
  257. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  258. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  259. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  260. package/src/components/custom/Workout/TempoDisplay.stories.tsx +22 -7
  261. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  262. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  263. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  264. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +7 -4
  265. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +4 -1
  266. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +4 -1
  267. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  268. package/src/components/custom/Workout/VelocityStrip.tsx +3 -0
  269. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  270. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  271. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  272. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  273. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  274. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  275. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  276. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  277. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  278. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  279. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  280. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  281. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  282. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  283. package/src/components/custom/Workout/ZoneTrack.stories.tsx +7 -6
  284. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  285. package/src/components/custom/Workout/index.ts +5 -0
  286. package/src/components/custom/Workout/metricText.stories.tsx +6 -5
  287. package/src/components/custom/Workout/metricText.tsx +4 -0
  288. package/src/components/custom/Workout/setHeadingKit.tsx +207 -26
  289. package/src/components/custom/charts/README.md +61 -0
  290. package/src/components/custom/charts/SetBarChart.test.tsx +30 -1
  291. package/src/components/custom/charts/SetBarChart.tsx +42 -4
  292. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  293. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  294. package/src/components/custom/charts/SparkBars.tsx +85 -0
  295. package/src/components/custom/charts/index.ts +3 -0
  296. package/src/components/custom/index.ts +3 -0
  297. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  298. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  299. package/src/components/custom/stepper/Stepper.tsx +13 -28
  300. package/src/components/icons/icons.stories.tsx +1 -1
  301. package/src/components/icons/icons.tsx +51 -0
  302. package/src/components/shell/AppShell.stories.tsx +150 -0
  303. package/src/components/shell/AppShell.test.tsx +70 -0
  304. package/src/components/shell/AppShell.tsx +90 -0
  305. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  306. package/src/components/shell/BrandLockup.test.tsx +34 -1
  307. package/src/components/shell/BrandLockup.tsx +27 -11
  308. package/src/components/shell/NavItem.stories.tsx +4 -4
  309. package/src/components/shell/NavItem.test.tsx +17 -0
  310. package/src/components/shell/NavItem.tsx +17 -5
  311. package/src/components/shell/README.md +89 -26
  312. package/src/components/shell/SideNav.stories.tsx +38 -7
  313. package/src/components/shell/SideNav.test.tsx +16 -8
  314. package/src/components/shell/SideNav.tsx +17 -17
  315. package/src/components/shell/TopBar.stories.tsx +44 -19
  316. package/src/components/shell/TopBar.test.tsx +34 -12
  317. package/src/components/shell/TopBar.tsx +61 -40
  318. package/src/components/shell/brands.tsx +66 -0
  319. package/src/components/shell/index.ts +9 -8
  320. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  321. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  322. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  323. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  324. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  325. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  326. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  327. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  328. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  329. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  330. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  331. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  332. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  333. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  334. package/src/components/shell/workout/index.ts +9 -0
  335. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  336. package/src/components/ui/README.md +110 -0
  337. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  338. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  339. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  340. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  341. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  342. package/src/components/ui/avatar/Avatar.tsx +2 -10
  343. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  344. package/src/components/ui/badge/Badge.test.tsx +19 -3
  345. package/src/components/ui/badge/Badge.tsx +48 -55
  346. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  347. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  348. package/src/components/ui/button/Button.stories.tsx +46 -12
  349. package/src/components/ui/button/Button.tsx +32 -25
  350. package/src/components/ui/card/Card.stories.tsx +90 -94
  351. package/src/components/ui/card/Card.test.tsx +80 -6
  352. package/src/components/ui/card/Card.tsx +150 -203
  353. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  354. package/src/components/ui/checkbox/Checkbox.tsx +6 -26
  355. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  356. package/src/components/ui/chip/Chip.test.tsx +10 -6
  357. package/src/components/ui/chip/Chip.tsx +76 -91
  358. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  359. package/src/components/ui/collapse/Collapse.tsx +6 -11
  360. package/src/components/ui/data-row/DataRow.stories.tsx +9 -2
  361. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  362. package/src/components/ui/data-row/DataRow.tsx +18 -7
  363. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  364. package/src/components/ui/divider/Divider.tsx +1 -5
  365. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  366. package/src/components/ui/drawer/Drawer.tsx +13 -30
  367. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  368. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  369. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  370. package/src/components/ui/form-field/FormField.tsx +10 -46
  371. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  372. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  373. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  374. package/src/components/ui/help-tip/index.ts +7 -1
  375. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  376. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  377. package/src/components/ui/icon-box/IconBox.tsx +2 -12
  378. package/src/components/ui/indicator/Indicator.stories.tsx +1 -1
  379. package/src/components/ui/indicator/Indicator.tsx +20 -14
  380. package/src/components/ui/input/Input.stories.tsx +3 -11
  381. package/src/components/ui/input/Input.test.tsx +2 -10
  382. package/src/components/ui/input/Input.tsx +10 -25
  383. package/src/components/ui/input/index.ts +7 -1
  384. package/src/components/ui/link/Link.stories.tsx +19 -7
  385. package/src/components/ui/list-item/ListItem.stories.tsx +11 -2
  386. package/src/components/ui/list-item/ListItem.tsx +2 -9
  387. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  388. package/src/components/ui/menu/Menu.test.tsx +2 -11
  389. package/src/components/ui/menu/Menu.tsx +12 -14
  390. package/src/components/ui/menu/index.ts +8 -1
  391. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  392. package/src/components/ui/modal/Modal.tsx +29 -19
  393. package/src/components/ui/pill/Pill.stories.tsx +58 -23
  394. package/src/components/ui/pill/Pill.test.tsx +101 -3
  395. package/src/components/ui/pill/Pill.tsx +164 -49
  396. package/src/components/ui/pill/index.ts +1 -1
  397. package/src/components/ui/popover/Popover.stories.tsx +5 -7
  398. package/src/components/ui/popover/Popover.test.tsx +1 -6
  399. package/src/components/ui/popover/Popover.tsx +20 -15
  400. package/src/components/ui/popover/index.ts +7 -1
  401. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  402. package/src/components/ui/progress/Progress.test.tsx +1 -5
  403. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  404. package/src/components/ui/radio/Radio.test.tsx +6 -2
  405. package/src/components/ui/radio/Radio.tsx +23 -22
  406. package/src/components/ui/section/Section.stories.tsx +5 -14
  407. package/src/components/ui/section/Section.test.tsx +1 -6
  408. package/src/components/ui/section/Section.tsx +1 -3
  409. package/src/components/ui/section/index.ts +1 -5
  410. package/src/components/ui/select/Select.stories.tsx +3 -13
  411. package/src/components/ui/select/Select.test.tsx +2 -11
  412. package/src/components/ui/select/Select.tsx +14 -14
  413. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  414. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  415. package/src/components/ui/skeleton/index.ts +1 -7
  416. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  417. package/src/components/ui/spinner/Spinner.tsx +1 -4
  418. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  419. package/src/components/ui/stack/Stack.tsx +1 -1
  420. package/src/components/ui/surface/Surface.stories.tsx +17 -5
  421. package/src/components/ui/surface/Surface.test.tsx +88 -28
  422. package/src/components/ui/surface/Surface.tsx +37 -87
  423. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  424. package/src/components/ui/surface/index.ts +8 -0
  425. package/src/components/ui/surface/resolveSurface.ts +98 -0
  426. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  427. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  428. package/src/components/ui/switch/Switch.tsx +7 -6
  429. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  430. package/src/components/ui/tabs/Tabs.tsx +7 -22
  431. package/src/components/ui/tabs/index.ts +1 -7
  432. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  433. package/src/components/ui/tile/Tile.tsx +3 -0
  434. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  435. package/src/components/ui/toast/Toast.test.tsx +2 -8
  436. package/src/components/ui/toast/Toast.tsx +43 -53
  437. package/src/components/ui/toast/index.ts +0 -1
  438. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  439. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  440. package/src/components/ui/toolbar-button/ToolbarButton.tsx +25 -43
  441. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  442. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  443. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  444. package/src/components/ui/tooltip/index.ts +1 -1
  445. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  446. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  447. package/src/components/ui/trigger/index.ts +2 -0
  448. package/src/stories/PresetShowcase.stories.tsx +100 -47
  449. package/src/test/no-device-internals.test.ts +3 -3
  450. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  451. package/src/theme/ColorPalettes.stories.tsx +62 -45
  452. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  453. package/src/theme/Depth.stories.tsx +132 -65
  454. package/src/theme/DepthCalibration.stories.tsx +23 -21
  455. package/src/theme/ThemeProvider.test.tsx +4 -4
  456. package/src/theme/barrel.ts +7 -6
  457. package/src/theme/color-stories.coverage.test.ts +1 -1
  458. package/src/theme/color-utils.ts +41 -29
  459. package/src/theme/config.completeness.test.ts +1 -1
  460. package/src/theme/config.ts +28 -0
  461. package/src/theme/elevation.test.ts +82 -56
  462. package/src/theme/elevation.ts +77 -561
  463. package/src/theme/global.css +30 -2
  464. package/src/theme/grey-ramp.test.ts +24 -8
  465. package/src/theme/lift.test.ts +39 -0
  466. package/src/theme/lift.ts +102 -0
  467. package/src/theme/materials.test.ts +5 -2
  468. package/src/theme/materials.ts +38 -4
  469. package/src/theme/native-theming-guard.test.ts +5 -5
  470. package/src/theme/presets/apply.ts +4 -0
  471. package/src/theme/presets/audiobook.ts +5 -2
  472. package/src/theme/presets/default.ts +4 -0
  473. package/src/theme/presets/index.ts +14 -1
  474. package/src/theme/presets/types.ts +1 -1
  475. package/src/theme/semantic-contrast.test.ts +13 -4
  476. package/src/theme/status-distinctness.test.ts +66 -0
  477. package/src/theme/surface-planes.test.ts +37 -0
  478. package/src/theme/surface-planes.ts +68 -0
  479. package/src/theme/token-export-guard.test.ts +1 -3
  480. package/src/theme/tokens/primitives.test.ts +15 -7
  481. package/src/theme/tokens/primitives.ts +65 -33
  482. package/src/theme/tokens/semantic.ts +71 -37
  483. package/src/theme/wash-ladder.test.ts +58 -0
  484. package/src/theme/workout-tokens.ts +1 -7
  485. package/src/types/jest-axe.d.ts +2 -6
  486. package/src/types/raw-md.d.ts +5 -0
  487. package/src/utils/colors.ts +4 -4
  488. package/src/utils/form.ts +1 -3
  489. package/src/utils/index.ts +2 -7
  490. package/src/utils/number-format.ts +17 -0
  491. package/src/utils/workout-format.ts +15 -6
  492. package/src/web-jsx/jsx-runtime.ts +1 -5
  493. package/tailwind.config.js +5 -0
  494. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  495. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  496. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  497. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  498. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  499. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  500. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  501. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  502. package/src/components/shell/DashboardShell.tsx +0 -75
  503. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  504. package/src/theme/elevation.stories.tsx +0 -346
  505. package/src/utils/useTheme.ts +0 -72
  506. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  507. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  508. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  509. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -2,32 +2,33 @@
2
2
  /**
3
3
  * LiveFatiguePanel — the aligned "Live panel v2": the loss-relative {@link VelocityHero}
4
4
  * (primary) beside the vertical {@link LiveFatigueCard} (secondary), flooded by a
5
- * coaching {@link LiveAuraFrame} whose category tracks the verdict state. The optional
6
- * lite exercise header sits above.
5
+ * coaching {@link LiveAuraFrame} whose category tracks the verdict state.
7
6
  *
8
7
  * The velocity hero's per-rep velocities are NOT on the fatigue model (they come from
9
8
  * the live-view velocity path), so they're passed as their own `velocity` prop.
9
+ *
10
+ * SURFACE (TD-03.59). Colour resolves from the enclosing `<Surface>` through
11
+ * {@link useOnSurfaceColor}, not from a module-scope `getSemanticColors('dark')`. Outside
12
+ * any Surface the context still defaults to the dark `base` plane, so the wall display
13
+ * renders exactly as before while a light surface now works.
14
+ *
15
+ * RESPONSIVE (TD-03.58). The panel is CONTAINER-responsive (SIZE-D01), not prop-sized: it
16
+ * measures its own width in `onLayout` and hands it to {@link panelLayout}, which owns the
17
+ * stack/expand tiers, the spacing and the card width. The one `bodyHeight` is split across
18
+ * the hero and the card by {@link panelBodySplit} — one call, so the two cannot disagree.
19
+ * Nothing here animates: a tier change is a re-layout, never a transition, because the panel
20
+ * is read from across a room mid-set.
10
21
  */
11
- import { View, Text } from 'react-native'
22
+ import { useState } from 'react'
23
+ import { View, Text, type LayoutChangeEvent } from 'react-native'
12
24
  import { LiveAuraFrame, type LiveAuraCategory } from '../Workout/LiveAuraFrame'
13
- import { getSemanticColors } from '../../../theme/tokens/semantic'
14
- import { alpha } from '../../../utils/colors'
25
+ import { useOnSurfaceColor } from '../../ui/surface'
15
26
  import { VelocityHero } from './VelocityHero'
16
27
  import { LiveFatigueCard } from './LiveFatigueCard'
17
- import { FONT_HEAD, FONT_UI, FONT_MONO, auraForVerdict } from './fatigue-tokens'
28
+ import { FONT_MONO, auraForVerdict } from './fatigue-tokens'
29
+ import { panelLayout, panelBodySplit } from './panel-layout'
18
30
  import type { LiveFatigueModel } from './fatigue-model'
19
31
 
20
- const t = getSemanticColors('dark')
21
-
22
- export interface LiveFatiguePanelHeader {
23
- /** Exercise name — the large title. */
24
- title: string
25
- /** Prescription / block line under the title. */
26
- subtitle?: string
27
- /** Right-aligned progress meta, e.g. "SET 3 · REP 7 / 8". */
28
- meta?: string
29
- }
30
-
31
32
  export interface LiveFatiguePanelVelocity {
32
33
  /** Per-rep MEAN concentric velocity (m/s), ordered by rep. */
33
34
  velocities: number[]
@@ -40,97 +41,63 @@ export interface LiveFatiguePanelProps {
40
41
  model: LiveFatigueModel
41
42
  /** The velocity-hero data (its own source — not on the fatigue model). */
42
43
  velocity: LiveFatiguePanelVelocity
43
- /** Optional lite exercise header. Omitted → no header row. */
44
- header?: LiveFatiguePanelHeader
45
44
  /** Aura-flood category. Defaults to the verdict-derived category. */
46
45
  aura?: LiveAuraCategory
47
46
  /** Panel body height in px. Default 508. */
48
47
  bodyHeight?: number
49
- /** Fixed fatigue-card column width. Default 318. */
48
+ /**
49
+ * Pin the fatigue-card column width instead of letting the tier choose it. Ignored when
50
+ * the panel is stacked (the card fills the content width there).
51
+ */
50
52
  cardWidth?: number
51
- }
52
-
53
- function ExerciseHeaderLite({ header }: { header: LiveFatiguePanelHeader }) {
54
- return (
55
- <View
56
- style={{
57
- flexDirection: 'row',
58
- alignItems: 'flex-end',
59
- justifyContent: 'space-between',
60
- paddingHorizontal: 24,
61
- paddingTop: 18,
62
- paddingBottom: 14,
63
- borderBottomWidth: 1,
64
- borderColor: alpha(t['text-primary'], 0.08),
65
- }}
66
- >
67
- <View style={{ gap: 2 }}>
68
- <Text
69
- style={{
70
- fontSize: 28,
71
- fontWeight: '700',
72
- fontFamily: FONT_HEAD,
73
- color: t['text-primary'],
74
- }}
75
- >
76
- {header.title}
77
- </Text>
78
- {header.subtitle && (
79
- <Text
80
- style={{
81
- fontSize: 13,
82
- fontWeight: '600',
83
- fontFamily: FONT_UI,
84
- color: t['text-secondary'],
85
- }}
86
- >
87
- {header.subtitle}
88
- </Text>
89
- )}
90
- </View>
91
- {header.meta && (
92
- <Text
93
- style={{
94
- fontSize: 13,
95
- fontWeight: '800',
96
- fontFamily: FONT_MONO,
97
- color: t['text-tertiary'],
98
- }}
99
- >
100
- {header.meta}
101
- </Text>
102
- )}
103
- </View>
104
- )
53
+ /**
54
+ * Override the measured container width, in px. The panel measures itself, so this is
55
+ * for tests (`onLayout` never fires under jsdom) and for a consumer that already knows
56
+ * the width it is about to hand the panel.
57
+ */
58
+ containerWidth?: number
105
59
  }
106
60
 
107
61
  export function LiveFatiguePanel({
108
62
  model,
109
63
  velocity,
110
- header,
111
64
  aura,
112
65
  bodyHeight = 508,
113
- cardWidth = 318,
66
+ cardWidth,
67
+ containerWidth,
114
68
  }: LiveFatiguePanelProps) {
69
+ const eyebrowColor = useOnSurfaceColor('tertiary')
70
+ const [measuredWidth, setMeasuredWidth] = useState(0)
71
+ const width = containerWidth ?? measuredWidth
72
+ const layout = panelLayout(width)
73
+ const { heroHeight, cardHeight } = panelBodySplit(bodyHeight, layout)
115
74
  const category = aura ?? auraForVerdict(model.verdict?.state ?? null)
116
- const heroH = bodyHeight - 26 // leaves room for the hero's own eyebrow above it
117
75
  return (
118
76
  <LiveAuraFrame
119
77
  category={category}
120
78
  style={{ borderRadius: 0, borderWidth: 0 }}
121
79
  testID="live-fatigue-panel"
80
+ onLayout={(e: LayoutChangeEvent) => setMeasuredWidth(e.nativeEvent.layout.width)}
122
81
  >
123
82
  <View style={{ flex: 1 }}>
124
- {header && <ExerciseHeaderLite header={header} />}
125
- <View style={{ padding: 24, flexDirection: 'row', gap: 18, alignItems: 'stretch' }}>
83
+ <View
84
+ testID="live-fatigue-body"
85
+ style={{
86
+ padding: layout.padding,
87
+ flexDirection: layout.stacked ? 'column' : 'row',
88
+ gap: layout.gap,
89
+ alignItems: 'stretch',
90
+ }}
91
+ >
126
92
  {/* PRIMARY — the velocity hero with VL bands; flexes to fill the width the card leaves. */}
127
93
  <View style={{ flex: 1, gap: 8 }}>
128
94
  <Text
95
+ testID="live-fatigue-eyebrow"
129
96
  style={{
130
97
  fontSize: 9,
131
98
  letterSpacing: 1.2,
132
99
  fontFamily: FONT_MONO,
133
- color: t['text-tertiary'],
100
+ color: eyebrowColor,
134
101
  }}
135
102
  >
136
103
  VELOCITY · this set
@@ -139,11 +106,15 @@ export function LiveFatiguePanel({
139
106
  velocities={velocity.velocities}
140
107
  targetReps={velocity.targetReps}
141
108
  liveRepIndex={velocity.liveRepIndex}
142
- height={heroH}
109
+ height={heroHeight}
143
110
  />
144
111
  </View>
145
- {/* SECONDARY — the vertical fatigue card. */}
146
- <LiveFatigueCard model={model} width={cardWidth} height={bodyHeight} />
112
+ {/* SECONDARY — the vertical fatigue card. Stacked, it fills the content width. */}
113
+ <LiveFatigueCard
114
+ model={model}
115
+ width={cardWidth ?? layout.cardWidth}
116
+ height={cardHeight}
117
+ />
147
118
  </View>
148
119
  </View>
149
120
  </LiveAuraFrame>
@@ -17,7 +17,7 @@ LiveFatiguePanel ← the composition (Live panel v2)
17
17
  └─ LiveFatigueCard ← secondary read (consumes LiveFatigueModel)
18
18
  ├─ VerdictHero (RPE number + verdict word, tone-flooded)
19
19
  ├─ FatigueLights (VEL/ROM/TEMPO "why" dots)
20
- │ ├─ StatusDot (Workout/ — the dot primitive, glow)
20
+ │ ├─ Indicator (ui/indicator — the dot primitive, glow)
21
21
  │ └─ Tooltip (ui/tooltip — hover detail)
22
22
  ├─ RomProgressionChart (per-rep silver/red depth bars + reference lines)
23
23
  ├─ GhostSpark (per-rep velocity-time sparkline; tempo EMBEDDED)
@@ -38,8 +38,11 @@ LiveFatiguePanel ← the composition (Live panel v2)
38
38
 
39
39
  ## Reuse audit — composed, not hand-rolled
40
40
 
41
- - **`StatusDot`** (Workout/) — the fatigue lights are `StatusDot` (glow) + a mono label
42
- inside a `Tooltip`; no re-implemented dot.
41
+ - **`Indicator`** (ui/indicator/) — the fatigue lights are an `Indicator` (glow) + a mono
42
+ label inside a `Tooltip`; no re-implemented dot. Roadmap decision 10 settled the two
43
+ competing dots in `Indicator`'s favour and this README used to name the loser.
44
+ `FatigueLights.tsx:12` still imports the deprecated `StatusDot`; that call site migrates
45
+ under AW-127 (`DEPRECATIONS.md`). Compose `Indicator` in anything new here.
43
46
  - **`Tooltip`** (ui/tooltip/) — the per-dimension hover detail.
44
47
  - **`VelocityStrip`** (Workout/) — the hero reuses it verbatim; `VelocityHero` only adds
45
48
  the loss-relative VL20/VL30 band overlay on the strip's own peak scale.
@@ -76,6 +79,34 @@ The reusable pure helpers (`ghostLineColor`, `auraForVerdict`, `mixHex`) live in
76
79
  - **Tempo carried by the ghost-spark phase BAND** (`PHASE_AXIS_COLOR`) — no mini-tempo
77
80
  digits, and no standalone hero-tempo component.
78
81
 
82
+ ## Responsive geometry (TD-03.58 / TD-03.60)
83
+
84
+ `panel-layout.ts` is the panel's single geometry source — a pure module with no
85
+ `react-native` import, so the tiers are unit-tested without `onLayout` (which never fires
86
+ under jsdom, gotcha 6). `LiveFatiguePanel` measures its own width (`SIZE-D01`:
87
+ container-driven, not a `size` prop) and calls it; `containerWidth` overrides the
88
+ measurement for tests and for a consumer that already knows the width.
89
+
90
+ | tier | width | layout | padding · gap | card width |
91
+ | --- | --- | --- | --- | --- |
92
+ | `xs` | < 600 | stacked | 16 · 12 | full content width |
93
+ | `sm` | 600–999 | stacked | 20 · 14 | full content width |
94
+ | `md` | 1000–1199 | row | 24 · 18 | 318 |
95
+ | `lg` | 1200–1919 | row | 24 · 18 | 318 |
96
+ | `xl` | ≥ 1920 | row | 24 · 18 | `0.22 × width`, clamped to 318–460 |
97
+
98
+ The edges are titan's own `primitiveBreakpoints`, asserted by identity in
99
+ `panel-layout.test.ts` so nobody can quietly swap in a hand-picked set. Padding and gap
100
+ are deliberately frozen from `md` up: voltras-mcp's `panel-geometry.ts` derives its stage
101
+ chrome from them, and moving them would overflow the wall stage.
102
+
103
+ `panelBodySplit` is the one-height rule: side by side, the hero and the card both take the
104
+ whole `bodyHeight`; stacked, they share it (card 0.55, gap taken out first). Both numbers
105
+ come from one call, so `bodyHeight` moves both or neither.
106
+
107
+ Nothing here animates. A tier change is a re-layout, never a transition — the panel is
108
+ read from across a room mid-set, and the visual baselines need a deterministic render.
109
+
79
110
  ## Data plan / follow-ups
80
111
 
81
112
  - **Store wiring:** `LiveFatigueCard` consumes `LiveFatigueModel` from voltras-mcp's
@@ -83,6 +114,12 @@ The reusable pure helpers (`ghostLineColor`, `auraForVerdict`, `mixHex`) live in
83
114
  hand-rolled `live-page/FatigueCard.tsx` spike. The velocity hero's per-rep velocities
84
115
  are NOT on the model (they come from the live-view velocity path) — passed as the
85
116
  panel's separate `velocity` prop.
117
+ - **Open — voltras-mcp duplicates the panel geometry:** `panel-geometry.ts` hardcodes
118
+ `FATIGUE_CARD_WIDTH = 318`, `PANEL_PAD = 24` and `HERO_EYEBROW_ALLOWANCE = 26` so the
119
+ idle stage can prefigure the panel. Those are now derivable — `panelLayout` /
120
+ `panelBodySplit` / `HERO_EYEBROW_ALLOWANCE` are exported from this family's barrel. Until
121
+ the SPA imports them, its idle stage will draw a 318 card placeholder where the live panel
122
+ draws an expanded one on an `xl` display.
86
123
  - **Deferred — VelocityStrip loss-relative bar fill:** the locked call also wants the
87
124
  hero's BAR FILL recoloured gold→orange→red by velocity loss (not absolute zone).
88
125
  VelocityStrip has no loss-relative colour mode; `VelocityHero` owns the band overlay
@@ -9,9 +9,9 @@ const PAGE_BG = greyRamp[975]
9
9
  const t = getSemanticColors('dark')
10
10
 
11
11
  const meta: Meta<typeof RomProgressionChart> = {
12
- title: 'Workout/Fatigue/ROM Progression',
12
+ title: 'Custom/Fatigue/ROM Progression',
13
13
  component: RomProgressionChart,
14
- tags: ['autodocs'],
14
+ tags: ['autodocs', 'status:candidate', '!status:review'],
15
15
  parameters: {
16
16
  docs: {
17
17
  description: {
@@ -1,6 +1,6 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  /**
3
- * `Workout/Fatigue/Silver-Red Palette` — the single documented source for the fatigue
3
+ * `Foundations/Color/Silver-Red Scheme` — the single documented source for the fatigue
4
4
  * family's line/quality colour language. SILVER when the rep is right, SHADES OF RED when
5
5
  * something's wrong — no greens, no ambers (those belong to the verdict tones + velocity-
6
6
  * loss bands, not here). Every swatch reads its value straight from `fatigue-tokens`, so
@@ -29,6 +29,7 @@ const t = getSemanticColors('dark')
29
29
 
30
30
  const meta: Meta = {
31
31
  title: 'Foundations/Color/Silver-Red Scheme',
32
+ tags: ['autodocs', 'status:candidate', '!status:review'],
32
33
  parameters: {
33
34
  layout: 'fullscreen',
34
35
  docs: {
@@ -10,9 +10,9 @@ const PANEL_BG = greyRamp[950]
10
10
  const t = getSemanticColors('dark')
11
11
 
12
12
  const meta: Meta<typeof VelocityHero> = {
13
- title: 'Workout/DataViz/VelocityHero',
13
+ title: 'Custom/Fatigue/Velocity Hero',
14
14
  component: VelocityHero,
15
- tags: ['autodocs'],
15
+ tags: ['autodocs', 'status:candidate', '!status:review'],
16
16
  parameters: {
17
17
  docs: {
18
18
  description: {
@@ -20,7 +20,8 @@ const meta: Meta<typeof VelocityHero> = {
20
20
  'The shipped `VelocityStrip` hero with LOSS-RELATIVE VL20/VL30 decision bands layered behind ' +
21
21
  'the bars (on the hero’s own peak scale). The velocity-loss language lives here so the ' +
22
22
  'fatigue card carries no separate VL chart. (Deferred: recolouring the bar FILL loss-relative ' +
23
- 'needs a new VelocityStrip prop — this component owns the band overlay only.)',
23
+ 'needs a new VelocityStrip prop — this component owns the band overlay only.)' +
24
+ ' Composes **VelocityStrip**.',
24
25
  },
25
26
  },
26
27
  },
@@ -9,9 +9,9 @@ const PAGE_BG = greyRamp[975]
9
9
  const t = getSemanticColors('dark')
10
10
 
11
11
  const meta: Meta<typeof VerdictHero> = {
12
- title: 'Workout/Fatigue/Verdict Hero',
12
+ title: 'Custom/Fatigue/Verdict Hero',
13
13
  component: VerdictHero,
14
- tags: ['autodocs'],
14
+ tags: ['autodocs', 'status:candidate', '!status:review'],
15
15
  parameters: {
16
16
  docs: {
17
17
  description: {
@@ -36,11 +36,6 @@ describe('buildMockPanelState', () => {
36
36
  }
37
37
  })
38
38
 
39
- it('counts the header rep out of the same planned total', () => {
40
- const { header } = buildMockPanelState(3)
41
- expect(header.meta).toBe(`SET 3 · REP 4 / ${MOCK_PLANNED_REPS}`)
42
- })
43
-
44
39
  it('derives the planned count from the rep data, not a literal', () => {
45
40
  expect(MOCK_PLANNED_REPS).toBe(MOCK_MEAN_VELOCITIES.length)
46
41
  })
@@ -177,8 +177,8 @@ export function buildMockModel(
177
177
  }
178
178
 
179
179
  /**
180
- * The WHOLE panel's input for a set truncated at `current` — model, velocity and header
181
- * from one rep count and one truncation point.
180
+ * The WHOLE panel's input for a set truncated at `current` — model and velocity from one
181
+ * rep count and one truncation point.
182
182
  *
183
183
  * The panel feeds two components that each draw their own upcoming-rep remainder, so
184
184
  * assembling their props separately is how the two halves drift apart. Build both here
@@ -190,7 +190,6 @@ export function buildMockPanelState(
190
190
  ): {
191
191
  model: LiveFatigueModel
192
192
  velocity: { velocities: number[]; targetReps: number; liveRepIndex: number }
193
- header: typeof MOCK_HEADER
194
193
  } {
195
194
  const model = buildMockModel(current, overrides)
196
195
  return {
@@ -201,7 +200,6 @@ export function buildMockPanelState(
201
200
  targetReps: MOCK_PLANNED_REPS,
202
201
  liveRepIndex: current,
203
202
  },
204
- header: { ...MOCK_HEADER, meta: `SET 3 · REP ${current + 1} / ${MOCK_PLANNED_REPS}` },
205
203
  }
206
204
  }
207
205
 
@@ -260,9 +258,3 @@ export const FATIGUE_STATES: Array<{ name: string; current: number; model: LiveF
260
258
 
261
259
  /** A warming-up (cold-start, < 2 reps) model — null verdict / RPE. */
262
260
  export const WARMING_UP_MODEL: LiveFatigueModel = buildMockModel(0, { rpe: null, verdict: null })
263
-
264
- export const MOCK_HEADER = {
265
- title: 'Cable Chest Press',
266
- subtitle: `Push A · Hypertrophy · 62 lb × ${MOCK_PLANNED_REPS} · tempo 2.6·0.4·0.95·0.28`,
267
- meta: `SET 3 · REP ${MOCK_PLANNED_REPS} / ${MOCK_PLANNED_REPS}`,
268
- }
@@ -12,6 +12,18 @@ const t = getSemanticColors('dark')
12
12
 
13
13
  export const FONT_HEAD = '"Space Grotesk", sans-serif'
14
14
  export const FONT_UI = '"Nunito Sans", sans-serif'
15
+
16
+ /**
17
+ * FOUNDATIONS GAP (TD-03.59 debt 3) — deliberately still the generic CSS fallback.
18
+ *
19
+ * titan has TWO mono declarations and they disagree, so adopting either here would be
20
+ * choosing a typeface rather than resolving a token:
21
+ * `primitiveTypography.fontFamilies.mono` SF Mono · Monaco · Inconsolata · Fira Mono · …
22
+ * `--font-family-mono` (global.css, both themes) SFMono-Regular · Consolas · Liberation Mono · Menlo
23
+ * They also pick different first faces on macOS, so the two render at different metrics.
24
+ * Reconciling them is a foundations decision; until it lands this stays `monospace` so the
25
+ * wall render does not move under a half-made choice.
26
+ */
15
27
  export const FONT_MONO = 'monospace'
16
28
 
17
29
  /** Per-dimension tone → semantic status colour (the one language the dots + hero share). */
@@ -1,11 +1,23 @@
1
1
  // Live-fatigue component family — the aligned "Live panel v2" hardened into titan
2
2
  // components. See ./README.md for the composition tree + reuse audit.
3
3
  export { LiveFatiguePanel } from './LiveFatiguePanel'
4
- export type {
5
- LiveFatiguePanelProps,
6
- LiveFatiguePanelHeader,
7
- LiveFatiguePanelVelocity,
8
- } from './LiveFatiguePanel'
4
+ export type { LiveFatiguePanelProps, LiveFatiguePanelVelocity } from './LiveFatiguePanel'
5
+ // The panel's responsive geometry, exported so a consumer that prefigures the panel (the
6
+ // wall SPA's idle stage) reads the same numbers instead of copying them.
7
+ export {
8
+ panelLayout,
9
+ panelBodySplit,
10
+ panelTier,
11
+ PANEL_BREAKPOINTS,
12
+ CARD_WIDTH_BASE,
13
+ CARD_WIDTH_MAX,
14
+ CARD_WIDTH_XL_RATIO,
15
+ CARD_HEIGHT_SHARE_STACKED,
16
+ HERO_EYEBROW_ALLOWANCE,
17
+ type PanelLayout,
18
+ type PanelBodySplit,
19
+ type PanelTier,
20
+ } from './panel-layout'
9
21
  export { LiveFatigueCard, type LiveFatigueCardProps } from './LiveFatigueCard'
10
22
  export { VerdictHero, type VerdictHeroProps } from './VerdictHero'
11
23
  export { FatigueLights, type FatigueLightsProps } from './FatigueLights'
@@ -0,0 +1,149 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { primitiveBreakpoints } from '../../../theme/tokens/primitives'
3
+ import {
4
+ PANEL_BREAKPOINTS,
5
+ CARD_WIDTH_BASE,
6
+ CARD_WIDTH_MAX,
7
+ CARD_WIDTH_XL_RATIO,
8
+ CARD_HEIGHT_SHARE_STACKED,
9
+ CARD_MIN_HEIGHT_STACKED,
10
+ CARD_MIN_CHART_HEIGHT,
11
+ CARD_MAX_CHART_HEIGHT,
12
+ CARD_NATURAL_CHART_HEIGHT,
13
+ HERO_EYEBROW_ALLOWANCE,
14
+ HERO_MIN_PLOT_HEIGHT,
15
+ panelTier,
16
+ panelLayout,
17
+ panelBodySplit,
18
+ cardChartHeight,
19
+ } from './panel-layout'
20
+
21
+ describe('panel breakpoints', () => {
22
+ // The edges are titan's, not this component's. If someone re-points the panel at a
23
+ // hand-picked set, this is where it shows up.
24
+ it('are titan primitiveBreakpoints, not values invented here', () => {
25
+ expect(PANEL_BREAKPOINTS).toBe(primitiveBreakpoints)
26
+ })
27
+
28
+ it.each([
29
+ [320, 'xs'],
30
+ [599, 'xs'],
31
+ [600, 'sm'],
32
+ [999, 'sm'],
33
+ [1000, 'md'],
34
+ [1199, 'md'],
35
+ [1200, 'lg'],
36
+ [1919, 'lg'],
37
+ [1920, 'xl'],
38
+ [2560, 'xl'],
39
+ ])('puts %ipx in the %s tier', (width, tier) => {
40
+ expect(panelTier(width)).toBe(tier)
41
+ })
42
+
43
+ it('treats an unmeasured container as md so the first paint is the row layout', () => {
44
+ expect(panelTier(0)).toBe('md')
45
+ expect(panelLayout(0).stacked).toBe(false)
46
+ expect(panelLayout(0).cardWidth).toBe(CARD_WIDTH_BASE)
47
+ })
48
+ })
49
+
50
+ describe('panelLayout', () => {
51
+ it('stacks below the md edge and rows at or above it', () => {
52
+ expect(panelLayout(PANEL_BREAKPOINTS.md - 1).stacked).toBe(true)
53
+ expect(panelLayout(PANEL_BREAKPOINTS.md).stacked).toBe(false)
54
+ })
55
+
56
+ it('holds the shipped padding, gap and card width at md and lg', () => {
57
+ for (const width of [1000, 1200, 1600, 1919]) {
58
+ expect(panelLayout(width)).toMatchObject({
59
+ stacked: false,
60
+ padding: 24,
61
+ gap: 18,
62
+ cardWidth: CARD_WIDTH_BASE,
63
+ })
64
+ }
65
+ })
66
+
67
+ it('expands the card at wall width without moving the padding or the gap', () => {
68
+ const wall = panelLayout(1920)
69
+ expect(wall.cardWidth).toBe(Math.round(1920 * CARD_WIDTH_XL_RATIO))
70
+ expect(wall.cardWidth).toBeGreaterThan(CARD_WIDTH_BASE)
71
+ // The SPA's stage chrome is derived from these two; moving them would overflow it.
72
+ expect(wall.padding).toBe(24)
73
+ expect(wall.gap).toBe(18)
74
+ })
75
+
76
+ it('caps the expansion so the card charts stay near the width they were drawn at', () => {
77
+ expect(panelLayout(4000).cardWidth).toBe(CARD_WIDTH_MAX)
78
+ })
79
+
80
+ it('gives a stacked card the full content width', () => {
81
+ const narrow = panelLayout(600)
82
+ expect(narrow.stacked).toBe(true)
83
+ expect(narrow.cardWidth).toBe(600 - narrow.padding * 2)
84
+ })
85
+ })
86
+
87
+ describe('panelBodySplit — one height source', () => {
88
+ // TD-03.60: the hero and the card must not be able to disagree about the body height.
89
+ it('moves BOTH the hero and the card when bodyHeight changes, in every tier', () => {
90
+ for (const width of [480, 900, 1000, 1440, 1920]) {
91
+ const layout = panelLayout(width)
92
+ const small = panelBodySplit(800, layout)
93
+ const large = panelBodySplit(1200, layout)
94
+ expect(large.heroHeight).toBeGreaterThan(small.heroHeight)
95
+ expect(large.cardHeight).toBeGreaterThan(small.cardHeight)
96
+ }
97
+ })
98
+
99
+ it('gives the row layout the full height to both, less the hero eyebrow', () => {
100
+ const split = panelBodySplit(508, panelLayout(1440))
101
+ expect(split.cardHeight).toBe(508)
102
+ expect(split.heroHeight).toBe(508 - HERO_EYEBROW_ALLOWANCE)
103
+ })
104
+
105
+ it('spends the whole stacked height on the two blocks and the gap, nothing lost', () => {
106
+ const layout = panelLayout(700)
107
+ const split = panelBodySplit(900, layout)
108
+ expect(split.heroHeight + HERO_EYEBROW_ALLOWANCE + split.cardHeight + layout.gap).toBe(900)
109
+ })
110
+
111
+ it('gives the stacked card the larger share once there is height to share', () => {
112
+ const layout = panelLayout(700)
113
+ const split = panelBodySplit(900, layout)
114
+ expect(split.cardHeight).toBe(Math.round((900 - layout.gap) * CARD_HEIGHT_SHARE_STACKED))
115
+ expect(split.cardHeight).toBeGreaterThan(split.heroHeight)
116
+ })
117
+
118
+ // A stacked card shorter than its content spilled the ghost spark outside the rounded
119
+ // edge — measured at 301px against a 384px content height before the floor existed.
120
+ it('never draws a stacked card below its content floor', () => {
121
+ for (const bodyHeight of [200, 400, 560, 600]) {
122
+ const split = panelBodySplit(bodyHeight, panelLayout(480))
123
+ expect(split.cardHeight).toBeGreaterThanOrEqual(CARD_MIN_HEIGHT_STACKED)
124
+ }
125
+ })
126
+
127
+ it('keeps the stacked hero plot readable rather than flattening it to nothing', () => {
128
+ const split = panelBodySplit(300, panelLayout(480))
129
+ expect(split.heroHeight).toBe(HERO_MIN_PLOT_HEIGHT)
130
+ })
131
+
132
+ it('never returns a negative height for a body too short to hold the chrome', () => {
133
+ const split = panelBodySplit(10, panelLayout(700))
134
+ expect(split.heroHeight).toBeGreaterThanOrEqual(0)
135
+ expect(split.cardHeight).toBeGreaterThanOrEqual(0)
136
+ })
137
+ })
138
+
139
+ describe('cardChartHeight', () => {
140
+ it('gives an unpinned card its natural plot height', () => {
141
+ expect(cardChartHeight(undefined)).toBe(CARD_NATURAL_CHART_HEIGHT)
142
+ })
143
+
144
+ it('clamps a pinned card between the floor and the ceiling', () => {
145
+ expect(cardChartHeight(100)).toBe(CARD_MIN_CHART_HEIGHT)
146
+ expect(cardChartHeight(5000)).toBe(CARD_MAX_CHART_HEIGHT)
147
+ expect(cardChartHeight(508)).toBe(Math.round(508 * 0.4))
148
+ })
149
+ })