@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,73 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { View } from 'react-native'
3
+ import { cn } from '../../../utils/cn'
4
+ import { Pill } from '../../ui/pill'
5
+ import { StatusDot } from '../Workout/StatusDot'
6
+ import { DateTime } from '../DateTime'
7
+ import { Typography } from '../Typography'
8
+ import { INITIATIVE_STATE_META, type InitiativeState } from './InitiativeCard'
9
+
10
+ export interface InitiativeHeaderProps {
11
+ title: string
12
+ /** Short slug shown under the title, e.g. `active-work`. */
13
+ slug: string
14
+ state: InitiativeState
15
+ /** Focused rank (1-based). Shown as `#N` for a ranked state. */
16
+ rank?: number
17
+ /** Ship-target label, e.g. `2026-Q3`. */
18
+ shipTarget?: string
19
+ /** ISO date the brief last changed. */
20
+ updated?: string
21
+ className?: string
22
+ }
23
+
24
+ /**
25
+ * InitiativeHeader — an initiative's identity line: title, slug, state, rank,
26
+ * ship target and when it was last touched.
27
+ *
28
+ * Shares the state vocabulary with {@link InitiativeCard} through
29
+ * `INITIATIVE_STATE_META`. Composes {@link StatusDot}, {@link Pill},
30
+ * {@link DateTime} and {@link Typography}. Used by the initiative reader.
31
+ */
32
+ export function InitiativeHeader({
33
+ title,
34
+ slug,
35
+ state,
36
+ rank,
37
+ shipTarget,
38
+ updated,
39
+ className,
40
+ }: InitiativeHeaderProps) {
41
+ const meta = INITIATIVE_STATE_META[state]
42
+ return (
43
+ <View className={cn('gap-1', className)}>
44
+ <Typography variant="h5" className="text-text-primary">
45
+ {title}
46
+ </Typography>
47
+ <View className="flex-row flex-wrap items-center gap-2.5">
48
+ <Typography variant="mono" className="text-text-tertiary">
49
+ {slug}
50
+ </Typography>
51
+ <StatusDot variant={meta.dot} size="sm" label={meta.label} />
52
+ {rank ? (
53
+ <Pill variant="subtle" color="primary" size="xs">
54
+ {`#${rank}`}
55
+ </Pill>
56
+ ) : null}
57
+ {shipTarget ? (
58
+ <Typography variant="caption" className="text-text-tertiary">
59
+ {`ship ${shipTarget}`}
60
+ </Typography>
61
+ ) : null}
62
+ {updated ? (
63
+ <View className="flex-row items-center gap-1">
64
+ <Typography variant="caption" className="text-text-tertiary">
65
+ updated
66
+ </Typography>
67
+ <DateTime value={updated} format="medium" isUTC variant="caption" color="tertiary" />
68
+ </View>
69
+ ) : null}
70
+ </View>
71
+ </View>
72
+ )
73
+ }
@@ -0,0 +1,145 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { Card } from '../../ui/card'
4
+ import { Surface } from '../../ui/surface'
5
+ import { Tabs, TabList, Tab, TabPanels, TabPanel } from '../../ui/tabs'
6
+ import { InitiativeHeader } from './InitiativeHeader'
7
+ import { InitiativeBrief, type InitiativeBriefData } from './InitiativeBrief'
8
+ import { OpenLoops, type OpenLoop } from './OpenLoops'
9
+ import { SessionList } from './SessionList'
10
+ import { TaskTable } from './TaskTable'
11
+ import type { TaskListItem } from './TaskRow'
12
+ import { sessionLinkers } from './session-linkers'
13
+ import { SESSION_FIXTURE, SESSION_NOW } from './session-fixture'
14
+ import {
15
+ INITIATIVE_BRIEF_FIXTURE,
16
+ INITIATIVE_LOOPS_FIXTURE,
17
+ INITIATIVE_NOW,
18
+ INITIATIVE_TASKS_FIXTURE,
19
+ } from './initiative-fixture'
20
+
21
+ interface ReaderArgs {
22
+ brief: InitiativeBriefData
23
+ loops: OpenLoop[]
24
+ tasks: TaskListItem[]
25
+ now: number
26
+ onPressTask: (id: string) => void
27
+ onPressLink: (name: string) => void
28
+ onPressPr: (number: number) => void
29
+ }
30
+
31
+ /**
32
+ * The pieces composed the way a host would: identity header, then the open
33
+ * loops (the durable current state), then the brief beside the tasks and
34
+ * recent sessions. There is deliberately no `InitiativeReader` organism; this
35
+ * story is the reference composition.
36
+ */
37
+ function InitiativeReader({
38
+ brief,
39
+ loops,
40
+ tasks,
41
+ now,
42
+ onPressTask,
43
+ onPressLink,
44
+ onPressPr,
45
+ }: ReaderArgs) {
46
+ const linkers = sessionLinkers({ onPressTask, onPressLink, onPressPr })
47
+ // A default Card already resolves the card plane from the page it is on and wears the lift.
48
+ // A `bg-*` className here would be silently discarded — Card writes its plane into `style`.
49
+ const cardClass = 'gap-1 p-4'
50
+ // A tab strip is the card's header: its own py-2 is the whole gap above the labels, so the card gives none.
51
+ const tabCardClass = `min-w-[360px] flex-1 ${cardClass} pt-2`
52
+ return (
53
+ <View className="gap-4">
54
+ <InitiativeHeader
55
+ title={brief.title}
56
+ slug={brief.slug}
57
+ state={brief.state as never}
58
+ rank={brief.rank ?? undefined}
59
+ shipTarget={brief.shipTarget ?? undefined}
60
+ updated={brief.updated}
61
+ />
62
+ <Card className={cardClass} testID="reader-card">
63
+ <OpenLoops loops={loops} now={now} linkers={linkers} />
64
+ </Card>
65
+ <View className="flex-row flex-wrap items-start gap-4">
66
+ <Card className={`min-w-[360px] flex-1 ${cardClass}`} testID="reader-card">
67
+ <InitiativeBrief brief={brief} linkers={linkers} />
68
+ </Card>
69
+ <Card className={tabCardClass} testID="reader-card">
70
+ <Tabs defaultIndex={0}>
71
+ <TabList>
72
+ <Tab>{`Tasks (${tasks.length})`}</Tab>
73
+ <Tab>{`Sessions (${SESSION_FIXTURE.length})`}</Tab>
74
+ </TabList>
75
+ <TabPanels>
76
+ <TabPanel>
77
+ <TaskTable tasks={tasks} now={now} hideLegend hideColumns={['slug']} label=" " />
78
+ </TabPanel>
79
+ <TabPanel>
80
+ <SessionList sessions={SESSION_FIXTURE} now={SESSION_NOW} label=" " />
81
+ </TabPanel>
82
+ </TabPanels>
83
+ </Tabs>
84
+ </Card>
85
+ </View>
86
+ </View>
87
+ )
88
+ }
89
+
90
+ /**
91
+ * **M2 · Initiative reader** — one initiative's durable state: the identity
92
+ * header, the open loops from the session ledger, the brief in collapsible
93
+ * sections, and its open tasks and recent sessions.
94
+ *
95
+ * Composes `InitiativeHeader` · `OpenLoops` · `InitiativeBrief` · `TaskTable`
96
+ * · `SessionList`, all over `MarkdownProse` and the session linkers.
97
+ */
98
+ const meta: Meta<ReaderArgs> = {
99
+ title: 'Custom/ActiveWork/InitiativeReader',
100
+ component: InitiativeReader,
101
+ tags: ['autodocs', 'status:candidate', '!status:review'],
102
+ args: {
103
+ brief: INITIATIVE_BRIEF_FIXTURE,
104
+ loops: INITIATIVE_LOOPS_FIXTURE,
105
+ tasks: INITIATIVE_TASKS_FIXTURE,
106
+ now: INITIATIVE_NOW,
107
+ },
108
+ argTypes: {
109
+ brief: { table: { disable: true } },
110
+ loops: { table: { disable: true } },
111
+ tasks: { table: { disable: true } },
112
+ now: { table: { disable: true } },
113
+ onPressTask: { action: 'onPressTask' },
114
+ onPressLink: { action: 'onPressLink' },
115
+ onPressPr: { action: 'onPressPr' },
116
+ },
117
+ decorators: [
118
+ (Story) => (
119
+ <Surface level="base" className="min-h-screen p-6" testID="page-surface">
120
+ <Story />
121
+ </Surface>
122
+ ),
123
+ ],
124
+ parameters: {
125
+ // Fullscreen: the page Surface is the host plane every card lifts off, so it owns the frame.
126
+ layout: 'fullscreen',
127
+ docs: {
128
+ description: {
129
+ component:
130
+ 'Composes **InitiativeHeader** · **OpenLoops** · **InitiativeBrief** · **TaskTable** · **SessionList**, over **MarkdownProse** and the session linkers. The host owns layout.',
131
+ },
132
+ },
133
+ },
134
+ }
135
+ export default meta
136
+
137
+ type Story = StoryObj<ReaderArgs>
138
+
139
+ /** The active-work initiative: five open loops, four open tasks, its v0.1 brief. */
140
+ export const Default: Story = {}
141
+
142
+ /** A quiet initiative: no open loops, so the section reads its empty state. */
143
+ export const NoOpenLoops: Story = {
144
+ args: { loops: [] },
145
+ }
@@ -0,0 +1,58 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Card } from '../../ui/card'
3
+ import { Surface } from '../../ui/surface'
4
+ import { OpenLoops } from './OpenLoops'
5
+ import { sessionLinkers } from './session-linkers'
6
+ import { INITIATIVE_LOOPS_FIXTURE, INITIATIVE_NOW } from './initiative-fixture'
7
+
8
+ /**
9
+ * **OpenLoops** — the initiative's hanging threads from the session ledger,
10
+ * each with its kind, age and auto-linked text. The durable current state a
11
+ * session picks up.
12
+ *
13
+ * Composes `Eyebrow` · `Pill` · `Divider` · `MarkdownProse`.
14
+ */
15
+ const meta: Meta<typeof OpenLoops> = {
16
+ title: 'Custom/ActiveWork/OpenLoops',
17
+ component: OpenLoops,
18
+ tags: ['autodocs', 'status:candidate', '!status:review'],
19
+ args: {
20
+ loops: INITIATIVE_LOOPS_FIXTURE,
21
+ now: INITIATIVE_NOW,
22
+ linkers: sessionLinkers(),
23
+ },
24
+ argTypes: {
25
+ loops: { table: { disable: true } },
26
+ now: { table: { disable: true } },
27
+ linkers: { table: { disable: true } },
28
+ },
29
+ decorators: [
30
+ (Story) => (
31
+ <Surface level="base" className="min-h-screen w-full max-w-[620px] p-6" testID="page-surface">
32
+ <Card className="gap-1 p-4">
33
+ <Story />
34
+ </Card>
35
+ </Surface>
36
+ ),
37
+ ],
38
+ parameters: {
39
+ layout: 'fullscreen',
40
+ docs: {
41
+ description: {
42
+ component:
43
+ 'Composes **Eyebrow** · **Pill** · **Divider** · **MarkdownProse**. Used-by ↑ the Initiative Reader composition.',
44
+ },
45
+ },
46
+ },
47
+ }
48
+ export default meta
49
+
50
+ type Story = StoryObj<typeof OpenLoops>
51
+
52
+ /** Five loops of mixed kind. */
53
+ export const Default: Story = {}
54
+
55
+ /** No open loops: the empty state. */
56
+ export const Empty: Story = {
57
+ args: { loops: [] },
58
+ }
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { fireEvent, render, screen, within } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { OpenLoops } from './OpenLoops'
5
+ import { sessionLinkers } from './session-linkers'
6
+ import { INITIATIVE_LOOPS_FIXTURE, INITIATIVE_NOW } from './initiative-fixture'
7
+
8
+ describe('OpenLoops', () => {
9
+ it('lists a row per loop under a count heading, inside a list', () => {
10
+ render(<OpenLoops loops={INITIATIVE_LOOPS_FIXTURE} now={INITIATIVE_NOW} />)
11
+ expect(screen.getByText('5 open loops')).toBeInTheDocument()
12
+ expect(screen.getByRole('list', { name: '5 open loops' })).toBeInTheDocument()
13
+ expect(screen.getAllByTestId('open-loop')).toHaveLength(INITIATIVE_LOOPS_FIXTURE.length)
14
+ })
15
+
16
+ it('labels a task loop with its target ref and a PR loop with its kind', () => {
17
+ render(<OpenLoops loops={INITIATIVE_LOOPS_FIXTURE} now={INITIATIVE_NOW} />)
18
+ const rows = screen.getAllByTestId('open-loop')
19
+ // n4 tracks AW-116; the PR-sweep loop (n3) carries the PR kind.
20
+ expect(within(rows[2]!).getByText('AW-116')).toBeInTheDocument()
21
+ expect(within(rows[1]!).getByText('PR')).toBeInTheDocument()
22
+ })
23
+
24
+ it('auto-links references in the loop text when linkers are supplied', () => {
25
+ const onPressTask = vi.fn()
26
+ render(
27
+ <OpenLoops
28
+ loops={INITIATIVE_LOOPS_FIXTURE}
29
+ now={INITIATIVE_NOW}
30
+ linkers={sessionLinkers({ onPressTask })}
31
+ />
32
+ )
33
+ // Row 3's text opens with an AW-22 ref; click the in-prose link, not the kind pill.
34
+ const proseRef = within(screen.getAllByTestId('open-loop')[3]!).getAllByTestId(
35
+ 'prose-ref-task'
36
+ )[0]!
37
+ fireEvent.click(proseRef)
38
+ expect(onPressTask).toHaveBeenCalledWith('AW-22')
39
+ })
40
+
41
+ it('singularises the heading and shows the empty state for no loops', () => {
42
+ const { rerender } = render(
43
+ <OpenLoops loops={INITIATIVE_LOOPS_FIXTURE.slice(0, 1)} now={INITIATIVE_NOW} />
44
+ )
45
+ expect(screen.getByText('1 open loop')).toBeInTheDocument()
46
+ rerender(<OpenLoops loops={[]} now={INITIATIVE_NOW} />)
47
+ expect(screen.getByText('No open loops')).toBeInTheDocument()
48
+ expect(screen.queryByTestId('open-loop')).not.toBeInTheDocument()
49
+ })
50
+
51
+ it('has no a11y violations', async () => {
52
+ const { container } = render(
53
+ <OpenLoops
54
+ loops={INITIATIVE_LOOPS_FIXTURE}
55
+ now={INITIATIVE_NOW}
56
+ linkers={sessionLinkers({ onPressTask: () => {} })}
57
+ />
58
+ )
59
+ expect(await axe(container)).toHaveNoViolations()
60
+ })
61
+ })
@@ -0,0 +1,107 @@
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 { cn } from '../../../utils/cn'
4
+ import { Divider } from '../../ui/divider'
5
+ import { Pill, type PillColor } from '../../ui/pill'
6
+ import { Typography } from '../Typography'
7
+ import { MarkdownProse, type ProseLinker } from '../Prose'
8
+ import { Eyebrow } from './Eyebrow'
9
+ import { formatTaskAge } from './format-time'
10
+
11
+ // RN's Role union omits 'list'/'listitem'; RNW passes them through to the DOM.
12
+ const LIST_ROLE = 'list' as ViewProps['role']
13
+ const LISTITEM_ROLE = 'listitem' as ViewProps['role']
14
+
15
+ /** What a loop is about, so the reader can colour and label its kind. */
16
+ export type OpenLoopKind = 'task' | 'pr' | 'prose'
17
+
18
+ /** One hanging thread from the session ledger, as the reader consumes it. */
19
+ export interface OpenLoop {
20
+ /** Stable `<session-file-stem>#<id>` reference. */
21
+ ref: string
22
+ kind: OpenLoopKind
23
+ /** The loop text, auto-linked like any prose. */
24
+ text: string
25
+ /** The task this loop tracks, when kind is `task`. */
26
+ targetRef?: string
27
+ /** ISO time the loop was opened; drives the age label. */
28
+ openedAt?: string
29
+ /** The session file that opened it. */
30
+ sessionFile?: string
31
+ }
32
+
33
+ const KIND_META: Record<OpenLoopKind, { label: string; color: PillColor }> = {
34
+ task: { label: 'task', color: 'primary' },
35
+ pr: { label: 'PR', color: 'info' },
36
+ prose: { label: 'note', color: 'default' },
37
+ }
38
+
39
+ export interface OpenLoopsProps {
40
+ loops: OpenLoop[]
41
+ /** Reference instant for the age labels, injected so renders are deterministic. */
42
+ now: number
43
+ /** Reference patterns to auto-link in each loop's text. */
44
+ linkers?: ProseLinker[]
45
+ /** Heading over the list. Defaults to the loop count. */
46
+ label?: string
47
+ /** Rendered when there are no open loops. */
48
+ emptyLabel?: string
49
+ className?: string
50
+ }
51
+
52
+ function LoopRow({ loop, now, linkers }: { loop: OpenLoop; now: number; linkers: ProseLinker[] }) {
53
+ const meta = KIND_META[loop.kind]
54
+ return (
55
+ <View className="gap-1 py-2" role={LISTITEM_ROLE} testID="open-loop">
56
+ <View className="flex-row items-center gap-2">
57
+ <Pill variant="subtle" color={meta.color} size="xs">
58
+ {loop.targetRef ?? meta.label}
59
+ </Pill>
60
+ {loop.openedAt ? (
61
+ <Typography variant="caption" className="leading-none text-text-tertiary">
62
+ {formatTaskAge(loop.openedAt, now)}
63
+ </Typography>
64
+ ) : null}
65
+ </View>
66
+ <MarkdownProse body={loop.text} linkers={linkers} />
67
+ </View>
68
+ )
69
+ }
70
+
71
+ /**
72
+ * OpenLoops — the initiative's hanging threads from the session ledger: each
73
+ * loop's kind, age and auto-linked text. This is the durable "current state"
74
+ * a session picks up, in place of a hand-maintained handoff document.
75
+ *
76
+ * Composes {@link Eyebrow}, {@link Pill}, {@link Divider} and
77
+ * {@link MarkdownProse}. Used by the initiative reader.
78
+ */
79
+ export function OpenLoops({
80
+ loops,
81
+ now,
82
+ linkers = [],
83
+ label,
84
+ emptyLabel = 'No open loops',
85
+ className,
86
+ }: OpenLoopsProps) {
87
+ const heading = label ?? `${loops.length} open ${loops.length === 1 ? 'loop' : 'loops'}`
88
+ return (
89
+ <View className={cn('gap-1', className)}>
90
+ <Eyebrow>{heading}</Eyebrow>
91
+ {loops.length === 0 ? (
92
+ <Typography variant="body2" className="py-2 text-text-tertiary">
93
+ {emptyLabel}
94
+ </Typography>
95
+ ) : (
96
+ <View role={LIST_ROLE} aria-label={heading}>
97
+ {loops.map((loop, i) => (
98
+ <View key={loop.ref}>
99
+ {i > 0 ? <Divider /> : null}
100
+ <LoopRow loop={loop} now={now} linkers={linkers} />
101
+ </View>
102
+ ))}
103
+ </View>
104
+ )}
105
+ </View>
106
+ )
107
+ }
@@ -0,0 +1,101 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Surface } from '../../ui/surface'
3
+ import { PortfolioOverview } from './PortfolioOverview'
4
+
5
+ const meta: Meta<typeof PortfolioOverview> = {
6
+ title: 'Custom/ActiveWork/PortfolioOverview',
7
+ component: PortfolioOverview,
8
+ tags: ['autodocs', 'status:candidate', '!status:review'],
9
+ decorators: [
10
+ (Story) => (
11
+ <Surface level="base" className="min-h-screen p-6" testID="page-surface">
12
+ <Story />
13
+ </Surface>
14
+ ),
15
+ ],
16
+ parameters: {
17
+ layout: 'fullscreen',
18
+ docs: {
19
+ description: {
20
+ component:
21
+ '**Organism.** At-a-glance status across every tracked initiative: a KPI row ' +
22
+ 'followed by initiative groups. Composes ' +
23
+ '[Card](?path=/docs/components-molecules-card--docs), ' +
24
+ '[Metric](?path=/docs/custom-metric--docs), ' +
25
+ '[Eyebrow](?path=/docs/components-molecules-eyebrow--docs), and ' +
26
+ '[InitiativeCard](?path=/docs/custom-activework-initiativecard--docs). ' +
27
+ 'Presentational only — the caller supplies `stats` and `sections`.',
28
+ },
29
+ },
30
+ },
31
+ args: {
32
+ title: 'Portfolio',
33
+ subtitle: '2 initiatives · 6 open tasks',
34
+ stats: [
35
+ { value: '1', label: 'Focused' },
36
+ { value: '6', label: 'Open tasks' },
37
+ { value: '2', label: 'Initiatives' },
38
+ { value: '2', label: 'With open work' },
39
+ ],
40
+ sections: [
41
+ {
42
+ heading: 'Focused · by rank',
43
+ items: [
44
+ {
45
+ title: 'active-work — durable workspace state',
46
+ slug: 'active-work',
47
+ state: 'focused',
48
+ rank: 1,
49
+ shipTarget: '2026-Q3',
50
+ openCount: 4,
51
+ severityCounts: { critical: 0, high: 1, medium: 2, low: 1 },
52
+ topTask: { id: 'AW-6', title: 'Linear / Jira / Slack discovery sources' },
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ heading: 'Backburner',
58
+ items: [
59
+ {
60
+ title: 'Computer organization — review legacy project dirs',
61
+ slug: 'computer-organization',
62
+ state: 'backburner',
63
+ openCount: 2,
64
+ severityCounts: { critical: 0, high: 0, medium: 0, low: 2 },
65
+ topTask: { id: 'CO-1', title: 'Review ~/Documents/projects/agents-skills' },
66
+ },
67
+ ],
68
+ },
69
+ ],
70
+ },
71
+ }
72
+ export default meta
73
+ type Story = StoryObj<typeof PortfolioOverview>
74
+
75
+ export const Default: Story = {}
76
+
77
+ export const NoOpenWork: Story = {
78
+ args: {
79
+ subtitle: '2 initiatives · 0 open tasks',
80
+ stats: [
81
+ { value: '0', label: 'Focused' },
82
+ { value: '0', label: 'Open tasks' },
83
+ { value: '2', label: 'Initiatives' },
84
+ { value: '0', label: 'With open work' },
85
+ ],
86
+ sections: [
87
+ {
88
+ heading: 'Backburner',
89
+ items: [
90
+ {
91
+ title: 'Audiobook app',
92
+ slug: 'audiobook',
93
+ state: 'backburner',
94
+ openCount: 0,
95
+ severityCounts: { critical: 0, high: 0, medium: 0, low: 0 },
96
+ },
97
+ ],
98
+ },
99
+ ],
100
+ },
101
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { PortfolioOverview } from './PortfolioOverview'
5
+
6
+ const stats = [
7
+ { value: '1', label: 'Focused' },
8
+ { value: '4', label: 'Open tasks' },
9
+ ]
10
+
11
+ const sections = [
12
+ {
13
+ heading: 'Focused · by rank',
14
+ items: [
15
+ {
16
+ title: 'active-work — durable workspace state',
17
+ slug: 'active-work',
18
+ state: 'focused' as const,
19
+ rank: 1,
20
+ openCount: 4,
21
+ severityCounts: { critical: 0, high: 1, medium: 2, low: 1 },
22
+ topTask: { id: 'AW-6', title: 'Discovery sources' },
23
+ },
24
+ ],
25
+ },
26
+ {
27
+ heading: 'Backburner',
28
+ items: [
29
+ {
30
+ title: 'Denver Rezzy',
31
+ slug: 'denver-rezzy',
32
+ state: 'backburner' as const,
33
+ openCount: 0,
34
+ severityCounts: { critical: 0, high: 0, medium: 0, low: 0 },
35
+ },
36
+ ],
37
+ },
38
+ ]
39
+
40
+ describe('PortfolioOverview', () => {
41
+ it('renders the title, subtitle, and KPI stats', () => {
42
+ render(
43
+ <PortfolioOverview
44
+ title="Portfolio"
45
+ subtitle="1 initiative · 4 open tasks"
46
+ stats={stats}
47
+ sections={[]}
48
+ />
49
+ )
50
+ expect(screen.getByText('Portfolio')).toBeInTheDocument()
51
+ expect(screen.getByText('1 initiative · 4 open tasks')).toBeInTheDocument()
52
+ expect(screen.getByText('Focused')).toBeInTheDocument()
53
+ expect(screen.getByText('Open tasks')).toBeInTheDocument()
54
+ })
55
+
56
+ it('renders every section heading and its initiative cards', () => {
57
+ render(<PortfolioOverview title="Portfolio" stats={stats} sections={sections} />)
58
+ expect(screen.getByText('Focused · by rank')).toBeInTheDocument()
59
+ // "Backburner" appears twice: the section eyebrow and the card's StatusDot label.
60
+ expect(screen.getAllByText('Backburner').length).toBeGreaterThanOrEqual(2)
61
+ expect(screen.getByText('active-work — durable workspace state')).toBeInTheDocument()
62
+ expect(screen.getByText('Denver Rezzy')).toBeInTheDocument()
63
+ })
64
+
65
+ it('renders with no sections at all', () => {
66
+ render(<PortfolioOverview title="Portfolio" stats={stats} sections={[]} />)
67
+ expect(screen.getByText('Portfolio')).toBeInTheDocument()
68
+ })
69
+
70
+ it('omits the subtitle when none is given', () => {
71
+ render(<PortfolioOverview title="Portfolio" stats={stats} sections={[]} />)
72
+ expect(screen.queryByText(/open tasks$/)).not.toBeInTheDocument()
73
+ })
74
+
75
+ it('has no accessibility violations', async () => {
76
+ const { container } = render(
77
+ <PortfolioOverview title="Portfolio" subtitle="Overview" stats={stats} sections={sections} />
78
+ )
79
+ expect(await axe(container)).toHaveNoViolations()
80
+ })
81
+ })