@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
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { fireEvent, render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { InitiativeBrief, splitBriefSections } from './InitiativeBrief'
5
+ import { INITIATIVE_BRIEF_FIXTURE } from './initiative-fixture'
6
+
7
+ describe('splitBriefSections', () => {
8
+ it('drops the leading # title and splits on ## headings', () => {
9
+ const sections = splitBriefSections('# Title\n\n## One\na\n\n## Two\nb')
10
+ expect(sections).toEqual([
11
+ { heading: 'One', body: 'a' },
12
+ { heading: 'Two', body: 'b' },
13
+ ])
14
+ })
15
+
16
+ it('keeps prose before the first ## as a lead section with no heading', () => {
17
+ const sections = splitBriefSections('# Title\nintro line\n\n## One\na')
18
+ expect(sections[0]).toEqual({ heading: '', body: 'intro line' })
19
+ expect(sections[1]!.heading).toBe('One')
20
+ })
21
+ })
22
+
23
+ describe('InitiativeBrief', () => {
24
+ it('opens only the first section, showing its body and the others as headings', () => {
25
+ render(<InitiativeBrief brief={INITIATIVE_BRIEF_FIXTURE} />)
26
+ expect(screen.getByText('Why this exists')).toBeInTheDocument()
27
+ expect(screen.getByText(/Engineering work spans days/)).toBeInTheDocument()
28
+ expect(screen.getByText('Stakeholders')).toBeInTheDocument()
29
+ expect(screen.queryByText(/@hjewkes/)).not.toBeInTheDocument()
30
+ })
31
+
32
+ it('opens a section on its heading and closes the previously open one', () => {
33
+ render(<InitiativeBrief brief={INITIATIVE_BRIEF_FIXTURE} />)
34
+ fireEvent.click(screen.getByText('Stakeholders'))
35
+ expect(screen.getByText(/@hjewkes/)).toBeInTheDocument()
36
+ expect(screen.queryByText(/Engineering work spans days/)).not.toBeInTheDocument()
37
+ })
38
+
39
+ it('steps through the sections with the prev/next controls', () => {
40
+ render(<InitiativeBrief brief={INITIATIVE_BRIEF_FIXTURE} />)
41
+ expect(screen.getByText(/1 \//)).toBeInTheDocument()
42
+ fireEvent.click(screen.getByRole('button', { name: 'Next section' }))
43
+ // The second section is now open; the first's body is gone.
44
+ expect(screen.queryByText(/Engineering work spans days/)).not.toBeInTheDocument()
45
+ expect(screen.getByText(/2 \//)).toBeInTheDocument()
46
+ fireEvent.click(screen.getByRole('button', { name: 'Previous section' }))
47
+ expect(screen.getByText(/Engineering work spans days/)).toBeInTheDocument()
48
+ })
49
+
50
+ it('disables prev on the first section', () => {
51
+ render(<InitiativeBrief brief={INITIATIVE_BRIEF_FIXTURE} />)
52
+ expect(screen.getByRole('button', { name: 'Previous section' })).toHaveAttribute(
53
+ 'aria-disabled',
54
+ 'true'
55
+ )
56
+ })
57
+
58
+ it('has no a11y violations', async () => {
59
+ const { container } = render(<InitiativeBrief brief={INITIATIVE_BRIEF_FIXTURE} />)
60
+ expect(await axe(container)).toHaveNoViolations()
61
+ })
62
+ })
@@ -0,0 +1,182 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { useMemo, useState } from 'react'
3
+ import { Pressable, View } from 'react-native'
4
+ import { cn } from '../../../utils/cn'
5
+ import { Typography } from '../Typography'
6
+ import { MarkdownProse, type ProseLinker } from '../Prose'
7
+ import { Eyebrow } from './Eyebrow'
8
+
9
+ /** An initiative's durable brief, as the reader consumes it. */
10
+ export interface InitiativeBriefData {
11
+ slug: string
12
+ title: string
13
+ state: string
14
+ rank?: number | null
15
+ shipTarget?: string | null
16
+ updated: string
17
+ /** The brief's markdown body. */
18
+ body: string
19
+ }
20
+
21
+ /** A brief section: an `##` heading and the prose beneath it, up to the next `##`. */
22
+ export interface BriefSection {
23
+ heading: string
24
+ body: string
25
+ }
26
+
27
+ export interface InitiativeBriefProps {
28
+ brief: InitiativeBriefData
29
+ /** Reference patterns to auto-link in the prose. */
30
+ linkers?: ProseLinker[]
31
+ className?: string
32
+ }
33
+
34
+ /**
35
+ * Splits a brief into its `##` sections, dropping the leading `#` title (the
36
+ * header already shows it). Anything before the first `##` becomes a leading
37
+ * section with an empty heading, so a brief with no sections still renders.
38
+ */
39
+ export function splitBriefSections(body: string): BriefSection[] {
40
+ const withoutTitle = body.replace(/^\s*#\s+.*(?:\n|$)/, '')
41
+ const sections: BriefSection[] = []
42
+ let current: BriefSection | null = null
43
+ for (const line of withoutTitle.split('\n')) {
44
+ const heading = line.match(/^##\s+(.*)/)
45
+ if (heading) {
46
+ if (current) sections.push(current)
47
+ current = { heading: heading[1]!.trim(), body: '' }
48
+ } else if (current) {
49
+ current.body += `${line}\n`
50
+ } else if (line.trim()) {
51
+ current = { heading: '', body: `${line}\n` }
52
+ }
53
+ }
54
+ if (current) sections.push(current)
55
+ return sections.map((s) => ({ ...s, body: s.body.trim() }))
56
+ }
57
+
58
+ /** A prev/next step control: a glyph in a small pressable, muted when it cannot move. */
59
+ function StepButton({
60
+ glyph,
61
+ label,
62
+ disabled,
63
+ onPress,
64
+ }: {
65
+ glyph: string
66
+ label: string
67
+ disabled: boolean
68
+ onPress: () => void
69
+ }) {
70
+ return (
71
+ <Pressable
72
+ onPress={disabled ? undefined : onPress}
73
+ accessibilityRole="button"
74
+ accessibilityLabel={label}
75
+ accessibilityState={{ disabled }}
76
+ disabled={disabled}
77
+ className={cn(
78
+ 'h-6 w-6 items-center justify-center rounded-md',
79
+ disabled ? '' : 'web:hover:bg-interactive-hover active:bg-interactive-active'
80
+ )}
81
+ >
82
+ {/* Was a tertiary text token under an opacity modifier, which emitted no rule (VW-308). */}
83
+ <Typography
84
+ variant="body2"
85
+ className={cn('leading-none', disabled ? 'text-text-disabled' : 'text-text-secondary')}
86
+ >
87
+ {glyph}
88
+ </Typography>
89
+ </Pressable>
90
+ )
91
+ }
92
+
93
+ function SectionHeading({
94
+ heading,
95
+ open,
96
+ onPress,
97
+ }: {
98
+ heading: string
99
+ open: boolean
100
+ onPress: () => void
101
+ }) {
102
+ return (
103
+ <Pressable
104
+ onPress={onPress}
105
+ accessibilityRole="button"
106
+ accessibilityState={{ expanded: open }}
107
+ testID="brief-section-heading"
108
+ className="flex-row items-center justify-between py-1.5"
109
+ >
110
+ <Typography
111
+ variant="subtitle2"
112
+ className={cn('font-bold', open ? 'text-text-primary' : 'text-text-secondary')}
113
+ >
114
+ {heading}
115
+ </Typography>
116
+ <Typography variant="caption" className="leading-none text-text-tertiary">
117
+ {open ? '▲' : '▼'}
118
+ </Typography>
119
+ </Pressable>
120
+ )
121
+ }
122
+
123
+ /**
124
+ * InitiativeBrief — an initiative's brief prose as a single-open accordion of
125
+ * its `##` sections: click any heading to open it, or step through them with
126
+ * the prev/next controls in the header, which close the current section and
127
+ * open the next. Prose before the first `##` always shows. References
128
+ * auto-link from `linkers`.
129
+ *
130
+ * Composes {@link Eyebrow}, {@link MarkdownProse} and {@link Typography}. Used
131
+ * by the initiative reader.
132
+ */
133
+ export function InitiativeBrief({ brief, linkers = [], className }: InitiativeBriefProps) {
134
+ const sections = useMemo(() => splitBriefSections(brief.body), [brief.body])
135
+ const lead = sections.filter((s) => !s.heading)
136
+ const steps = sections.filter((s) => s.heading)
137
+ const [open, setOpen] = useState(0)
138
+ const clamped = Math.min(open, steps.length - 1)
139
+ return (
140
+ <View className={cn('gap-1', className)} testID="initiative-brief">
141
+ <View className="flex-row items-center justify-between">
142
+ <Eyebrow>Brief</Eyebrow>
143
+ {steps.length > 1 ? (
144
+ <View className="flex-row items-center gap-1">
145
+ <Typography variant="caption" className="leading-none text-text-tertiary">
146
+ {`${clamped + 1} / ${steps.length}`}
147
+ </Typography>
148
+ <StepButton
149
+ glyph="‹"
150
+ label="Previous section"
151
+ disabled={clamped <= 0}
152
+ onPress={() => setOpen(clamped - 1)}
153
+ />
154
+ <StepButton
155
+ glyph="›"
156
+ label="Next section"
157
+ disabled={clamped >= steps.length - 1}
158
+ onPress={() => setOpen(clamped + 1)}
159
+ />
160
+ </View>
161
+ ) : null}
162
+ </View>
163
+ {lead.map((section, i) => (
164
+ <MarkdownProse key={`lead-${i}`} body={section.body} linkers={linkers} />
165
+ ))}
166
+ {steps.map((section, i) => (
167
+ <View key={section.heading}>
168
+ <SectionHeading
169
+ heading={section.heading}
170
+ open={i === clamped}
171
+ onPress={() => setOpen(i)}
172
+ />
173
+ {i === clamped ? (
174
+ <View className="pb-1">
175
+ <MarkdownProse body={section.body} linkers={linkers} />
176
+ </View>
177
+ ) : null}
178
+ </View>
179
+ ))}
180
+ </View>
181
+ )
182
+ }
@@ -0,0 +1,73 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Surface } from '../../ui/surface'
3
+ import { InitiativeCard } from './InitiativeCard'
4
+
5
+ const meta: Meta<typeof InitiativeCard> = {
6
+ title: 'Custom/ActiveWork/InitiativeCard',
7
+ component: InitiativeCard,
8
+ tags: ['autodocs', 'status:candidate', '!status:review'],
9
+ args: {
10
+ title: 'active-work — durable workspace state',
11
+ slug: 'active-work',
12
+ state: 'focused',
13
+ rank: 1,
14
+ shipTarget: '2026-Q3',
15
+ openCount: 4,
16
+ severityCounts: { critical: 0, high: 1, medium: 2, low: 1 },
17
+ topTask: { id: 'AW-6', title: 'Linear / Jira / Slack discovery sources' },
18
+ },
19
+ argTypes: {
20
+ state: { control: 'select', options: ['focused', 'backburner', 'paused', 'done'] },
21
+ rank: { control: 'number' },
22
+ shipTarget: { control: 'text' },
23
+ openCount: { control: 'number' },
24
+ severityCounts: { control: 'object' },
25
+ topTask: { control: 'object' },
26
+ },
27
+ decorators: [
28
+ (Story) => (
29
+ <Surface level="base" className="min-h-screen p-6" testID="page-surface">
30
+ <Story />
31
+ </Surface>
32
+ ),
33
+ ],
34
+ parameters: {
35
+ layout: 'fullscreen',
36
+ docs: {
37
+ description: {
38
+ component:
39
+ '**Card.** An at-a-glance summary of one initiative: state, rank, open-task ' +
40
+ 'count, a severity-mix bar, and its top-priority open task. Composes ' +
41
+ '[Card](?path=/docs/components-molecules-card--docs), ' +
42
+ '[Pill](?path=/docs/components-atoms-pill--docs), StatusDot, and SegmentedBar ' +
43
+ '(from the Workout family). Used by ' +
44
+ '[PortfolioOverview](?path=/docs/custom-activework-portfoliooverview--docs).',
45
+ },
46
+ },
47
+ },
48
+ }
49
+ export default meta
50
+ type Story = StoryObj<typeof InitiativeCard>
51
+
52
+ export const Default: Story = {}
53
+
54
+ export const NoOpenTasks: Story = {
55
+ args: {
56
+ state: 'done',
57
+ rank: undefined,
58
+ openCount: 0,
59
+ severityCounts: { critical: 0, high: 0, medium: 0, low: 0 },
60
+ topTask: undefined,
61
+ },
62
+ }
63
+
64
+ export const Backburner: Story = {
65
+ args: {
66
+ state: 'backburner',
67
+ rank: undefined,
68
+ shipTarget: undefined,
69
+ openCount: 2,
70
+ severityCounts: { critical: 0, high: 0, medium: 0, low: 2 },
71
+ topTask: { id: 'CO-1', title: 'Review ~/Documents/projects/agents-skills — archived' },
72
+ },
73
+ }
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { InitiativeCard, type InitiativeState } from './InitiativeCard'
5
+
6
+ const base = {
7
+ title: 'active-work — durable workspace state',
8
+ slug: 'active-work',
9
+ openCount: 4,
10
+ severityCounts: { critical: 0, high: 1, medium: 2, low: 1 },
11
+ }
12
+
13
+ describe('InitiativeCard', () => {
14
+ it('renders title, slug, and open count', () => {
15
+ render(<InitiativeCard {...base} state="focused" />)
16
+ expect(screen.getByText(base.title)).toBeInTheDocument()
17
+ expect(screen.getByText(base.slug)).toBeInTheDocument()
18
+ expect(screen.getByText('4 open')).toBeInTheDocument()
19
+ })
20
+
21
+ it('renders a rank pill only when rank is given', () => {
22
+ const { rerender } = render(<InitiativeCard {...base} state="focused" rank={1} />)
23
+ expect(screen.getByText('#1')).toBeInTheDocument()
24
+ rerender(<InitiativeCard {...base} state="focused" />)
25
+ expect(screen.queryByText('#1')).not.toBeInTheDocument()
26
+ })
27
+
28
+ it('renders the top task when given', () => {
29
+ render(
30
+ <InitiativeCard
31
+ {...base}
32
+ state="focused"
33
+ topTask={{ id: 'AW-6', title: 'Discovery sources' }}
34
+ />
35
+ )
36
+ expect(screen.getByText('AW-6')).toBeInTheDocument()
37
+ expect(screen.getByText('Discovery sources')).toBeInTheDocument()
38
+ })
39
+
40
+ it('renders a fallback when there is no top task', () => {
41
+ render(<InitiativeCard {...base} state="focused" topTask={undefined} />)
42
+ expect(screen.getByText('no open tasks')).toBeInTheDocument()
43
+ })
44
+
45
+ it('labels every lifecycle state', () => {
46
+ const states: InitiativeState[] = ['focused', 'backburner', 'paused', 'done']
47
+ for (const state of states) {
48
+ const { unmount } = render(<InitiativeCard {...base} state={state} />)
49
+ unmount()
50
+ }
51
+ })
52
+
53
+ it('omits the severity bar when all counts are zero', () => {
54
+ render(
55
+ <InitiativeCard
56
+ {...base}
57
+ state="done"
58
+ severityCounts={{ critical: 0, high: 0, medium: 0, low: 0 }}
59
+ />
60
+ )
61
+ expect(screen.queryByTestId('segmented-bar-segment')).not.toBeInTheDocument()
62
+ })
63
+
64
+ it('has no accessibility violations', async () => {
65
+ const { container } = render(<InitiativeCard {...base} state="focused" rank={1} />)
66
+ expect(await axe(container)).toHaveNoViolations()
67
+ })
68
+ })
@@ -0,0 +1,136 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { View, type ViewProps } from 'react-native'
3
+ import { Card } from '../../ui/card'
4
+ import { Pill } from '../../ui/pill'
5
+ import { StatusDot, type StatusDotVariant } from '../Workout/StatusDot'
6
+ import { SegmentedBar, type SegmentedBarSegment } from '../Workout/SegmentedBar'
7
+ import { Typography } from '../Typography'
8
+ import { SEVERITY_BAR_COLOR, SEVERITY_ORDER, type TaskSeverity } from './SeverityLabel'
9
+
10
+ export type InitiativeState = 'focused' | 'backburner' | 'paused' | 'done'
11
+
12
+ /** Initiative state → label + {@link StatusDot} variant. One owner; the initiative header reads it too. */
13
+ export const INITIATIVE_STATE_META: Record<
14
+ InitiativeState,
15
+ { label: string; dot: StatusDotVariant }
16
+ > = {
17
+ focused: { label: 'Focused', dot: 'on-track' },
18
+ backburner: { label: 'Backburner', dot: 'future' },
19
+ paused: { label: 'Paused', dot: 'deviation' },
20
+ done: { label: 'Done', dot: 'success' },
21
+ }
22
+
23
+ const STATE_META = INITIATIVE_STATE_META
24
+
25
+ export interface InitiativeCardTopTask {
26
+ id: string
27
+ title: string
28
+ }
29
+
30
+ export interface InitiativeCardProps extends ViewProps {
31
+ /** Initiative title, e.g. "active-work — durable workspace state". */
32
+ title: string
33
+ /** Short slug shown under the title, e.g. "active-work". */
34
+ slug: string
35
+ /** Lifecycle state — drives the status dot and card accent. */
36
+ state: InitiativeState
37
+ /** Focused rank (1-based). Omit for non-ranked states. */
38
+ rank?: number
39
+ /** Ship-target label, e.g. "2026-Q3". */
40
+ shipTarget?: string
41
+ /** Count of open tasks for this initiative. */
42
+ openCount: number
43
+ /** Open-task counts by severity, driving the segmented mix bar. Bar is omitted when all-zero. */
44
+ severityCounts: Record<TaskSeverity, number>
45
+ /** The highest-priority open task, if any. Renders "no open tasks" when omitted. */
46
+ topTask?: InitiativeCardTopTask
47
+ className?: string
48
+ }
49
+
50
+ /**
51
+ * InitiativeCard — an at-a-glance summary of one active-work initiative: state,
52
+ * rank, open-task count, a severity-mix bar, and its top-priority open task.
53
+ * Composes Card / Pill / StatusDot / SegmentedBar / Typography — never
54
+ * hand-rolled. Used by {@link PortfolioOverview}.
55
+ *
56
+ * Sits on the card plane above the page like every other content card; the
57
+ * `accent` stripe is reserved for the focused state, where it carries meaning.
58
+ */
59
+ export function InitiativeCard({
60
+ title,
61
+ slug,
62
+ state,
63
+ rank,
64
+ shipTarget,
65
+ openCount,
66
+ severityCounts,
67
+ topTask,
68
+ className,
69
+ ...props
70
+ }: InitiativeCardProps) {
71
+ const meta = STATE_META[state]
72
+ const segments: SegmentedBarSegment[] = SEVERITY_ORDER.filter((k) => severityCounts[k] > 0).map(
73
+ (k) => ({ weight: severityCounts[k], color: SEVERITY_BAR_COLOR[k] })
74
+ )
75
+
76
+ return (
77
+ <Card
78
+ variant={state === 'focused' ? 'accent' : 'elevated'}
79
+ accentColor={state === 'focused' ? 'var(--color-brand-primary)' : undefined}
80
+ className={`w-[326px] gap-2.5 p-4 ${className ?? ''}`}
81
+ testID="initiative-card"
82
+ {...props}
83
+ >
84
+ <View className="flex-row items-start justify-between gap-2">
85
+ <View className="flex-1">
86
+ <Typography variant="subtitle2" className="text-sm font-bold text-text-primary">
87
+ {title}
88
+ </Typography>
89
+ <Typography variant="caption" className="text-xs text-text-tertiary">
90
+ {slug}
91
+ </Typography>
92
+ </View>
93
+ {rank ? (
94
+ <Pill variant="subtle" color="primary" size="xs">
95
+ {`#${rank}`}
96
+ </Pill>
97
+ ) : null}
98
+ </View>
99
+
100
+ <View className="flex-row items-center gap-3">
101
+ <StatusDot variant={meta.dot} size="sm" label={meta.label} />
102
+ <Typography variant="mono" className="text-xs text-text-secondary">
103
+ {openCount} open
104
+ </Typography>
105
+ {shipTarget ? (
106
+ <Typography variant="caption" className="text-xs text-text-tertiary">
107
+ {`ship ${shipTarget}`}
108
+ </Typography>
109
+ ) : null}
110
+ </View>
111
+
112
+ {segments.length > 0 ? (
113
+ <SegmentedBar segments={segments} height={5} gap={0} radius={9999} />
114
+ ) : null}
115
+
116
+ {topTask ? (
117
+ <View className="flex-row items-center gap-2">
118
+ <Typography variant="mono" className="text-xs text-brand-primary">
119
+ {topTask.id}
120
+ </Typography>
121
+ <Typography
122
+ variant="body2"
123
+ numberOfLines={1}
124
+ className="flex-1 text-xs text-text-secondary"
125
+ >
126
+ {topTask.title}
127
+ </Typography>
128
+ </View>
129
+ ) : (
130
+ <Typography variant="caption" className="text-xs text-text-tertiary">
131
+ no open tasks
132
+ </Typography>
133
+ )}
134
+ </Card>
135
+ )
136
+ }
@@ -0,0 +1,59 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Surface } from '../../ui/surface'
3
+ import { InitiativeHeader } from './InitiativeHeader'
4
+
5
+ /**
6
+ * **InitiativeHeader** — an initiative's identity line: title, slug, state,
7
+ * rank, ship target and last-touched date.
8
+ *
9
+ * Composes `StatusDot` · `Pill` · `DateTime` · `Typography`. Shares the state
10
+ * vocabulary with `InitiativeCard`.
11
+ */
12
+ const meta: Meta<typeof InitiativeHeader> = {
13
+ title: 'Custom/ActiveWork/InitiativeHeader',
14
+ component: InitiativeHeader,
15
+ tags: ['autodocs', 'status:candidate', '!status:review'],
16
+ args: {
17
+ title: 'active-work — durable workspace state',
18
+ slug: 'active-work',
19
+ state: 'focused',
20
+ rank: 1,
21
+ shipTarget: '2026-Q3',
22
+ updated: '2026-07-12',
23
+ },
24
+ argTypes: {
25
+ state: { control: 'inline-radio', options: ['focused', 'backburner', 'paused', 'done'] },
26
+ },
27
+ decorators: [
28
+ (Story) => (
29
+ <Surface level="base" className="min-h-screen w-full max-w-[760px] p-6" testID="page-surface">
30
+ <Story />
31
+ </Surface>
32
+ ),
33
+ ],
34
+ parameters: {
35
+ layout: 'fullscreen',
36
+ docs: {
37
+ description: {
38
+ component:
39
+ 'Composes **StatusDot** · **Pill** · **DateTime** · **Typography**. Used-by ↑ the Initiative Reader composition.',
40
+ },
41
+ },
42
+ },
43
+ }
44
+ export default meta
45
+
46
+ type Story = StoryObj<typeof InitiativeHeader>
47
+
48
+ /** A ranked, focused initiative. */
49
+ export const Default: Story = {}
50
+
51
+ /** A backburner initiative: no rank, no ship target. */
52
+ export const Backburner: Story = {
53
+ args: { state: 'backburner', rank: undefined, shipTarget: undefined },
54
+ }
55
+
56
+ /** A finished initiative. */
57
+ export const Done: Story = {
58
+ args: { state: 'done', rank: undefined },
59
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { InitiativeHeader } from './InitiativeHeader'
5
+
6
+ describe('InitiativeHeader', () => {
7
+ it('renders the title, slug, state, rank, ship target and updated date', () => {
8
+ render(
9
+ <InitiativeHeader
10
+ title="active-work — durable workspace state"
11
+ slug="active-work"
12
+ state="focused"
13
+ rank={1}
14
+ shipTarget="2026-Q3"
15
+ updated="2026-07-12"
16
+ />
17
+ )
18
+ expect(screen.getByText('active-work — durable workspace state')).toBeInTheDocument()
19
+ expect(screen.getByText('active-work')).toBeInTheDocument()
20
+ expect(screen.getByText('Focused')).toBeInTheDocument()
21
+ expect(screen.getByText('#1')).toBeInTheDocument()
22
+ expect(screen.getByText('ship 2026-Q3')).toBeInTheDocument()
23
+ expect(screen.getByText('Jul 12, 2026')).toBeInTheDocument()
24
+ })
25
+
26
+ it('drops rank, ship target and updated when absent', () => {
27
+ render(<InitiativeHeader title="Quiet" slug="quiet" state="backburner" />)
28
+ expect(screen.getByText('Backburner')).toBeInTheDocument()
29
+ expect(screen.queryByText(/^#/)).not.toBeInTheDocument()
30
+ expect(screen.queryByText(/^ship/)).not.toBeInTheDocument()
31
+ expect(screen.queryByText('updated')).not.toBeInTheDocument()
32
+ })
33
+
34
+ it('has no a11y violations', async () => {
35
+ const { container } = render(
36
+ <InitiativeHeader title="active-work" slug="active-work" state="focused" rank={1} />
37
+ )
38
+ expect(await axe(container)).toHaveNoViolations()
39
+ })
40
+ })