@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,321 @@
1
+ // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
+ import { useMemo } from 'react'
3
+ import { View } from 'react-native'
4
+ import { cn } from '../../../utils/cn'
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableHeader,
9
+ TableHeaderCell,
10
+ TableRow,
11
+ useColumnFit,
12
+ useMeasuredWidth,
13
+ useTable,
14
+ type TableColumnFit,
15
+ type TableComparator,
16
+ } from '../Table'
17
+ import { Typography } from '../Typography'
18
+ import { Eyebrow } from './Eyebrow'
19
+ import { SeverityLabel, SEVERITY_ORDER, severityRank, type TaskSeverity } from './SeverityLabel'
20
+ import { TaskRow, TASK_COLUMN_WIDTHS, type TaskListItem } from './TaskRow'
21
+
22
+ import { formatTaskAge } from './format-time'
23
+
24
+ // Kept on this module's surface: the session reader shares the helper now, but callers imported it from here first.
25
+ export { formatTaskAge }
26
+
27
+ type TaskSortKey = 'slug' | 'id' | 'title' | 'severity' | 'priority' | 'estimate' | 'updated'
28
+
29
+ /** Any column the table can leave out; `title` is the one column that always renders. */
30
+ export type TaskColumnKey = Exclude<TaskSortKey, 'title'> | 'tags'
31
+
32
+ interface TaskColumn extends TableColumnFit {
33
+ key: TaskSortKey | 'tags'
34
+ label: string
35
+ /** Full name behind an abbreviated label, shown on hover. */
36
+ tooltip?: string
37
+ width?: number
38
+ align?: 'left' | 'right'
39
+ sortable: boolean
40
+ }
41
+
42
+ const NO_HIDDEN_COLUMNS: TaskColumnKey[] = []
43
+
44
+ /**
45
+ * What leaves first when the table cannot fit everything. Metadata goes before
46
+ * anything that names the task: a row with no title is not a shorter row, it is
47
+ * a useless one, so `title` and `id` carry no priority and never drop. `slug`
48
+ * goes last of the droppable columns because the backlog reads across
49
+ * initiatives, so losing it costs more than losing the age.
50
+ */
51
+ const TASK_DROP_ORDER = {
52
+ severity: 1,
53
+ priority: 2,
54
+ estimate: 3,
55
+ tags: 4,
56
+ updated: 5,
57
+ slug: 6,
58
+ } as const
59
+
60
+ /** The hairline frame the grid sits inside, which the measured width includes but the columns cannot use. */
61
+ const GRID_FRAME_INSET = 2
62
+
63
+ /** How the severity column renders: `auto` collapses to the dot below {@link COMPACT_SEVERITY_BELOW}. */
64
+ export type SeverityDisplay = 'auto' | 'full' | 'dot'
65
+
66
+ /**
67
+ * Table width under which severity collapses to its dot. Below this the fixed
68
+ * columns leave the flexible title under ~220px, which is where titles stop
69
+ * being scannable; the dot gives 68px back.
70
+ */
71
+ export const COMPACT_SEVERITY_BELOW = 840
72
+
73
+ const SEVERITY_COLUMN: Record<'full' | 'dot', TaskColumn> = {
74
+ full: {
75
+ key: 'severity',
76
+ label: 'Severity',
77
+ width: TASK_COLUMN_WIDTHS.severity,
78
+ minWidth: TASK_COLUMN_WIDTHS.severity,
79
+ dropPriority: TASK_DROP_ORDER.severity,
80
+ sortable: true,
81
+ },
82
+ dot: {
83
+ key: 'severity',
84
+ label: 'Sev',
85
+ tooltip: 'Severity',
86
+ width: TASK_COLUMN_WIDTHS.severityCompact,
87
+ minWidth: TASK_COLUMN_WIDTHS.severityCompact,
88
+ dropPriority: TASK_DROP_ORDER.severity,
89
+ sortable: true,
90
+ },
91
+ }
92
+
93
+ const taskColumns = (dotOnly: boolean, hidden: TaskColumnKey[]): TaskColumn[] =>
94
+ allTaskColumns(dotOnly).filter((col) => !hidden.includes(col.key as TaskColumnKey))
95
+
96
+ const allTaskColumns = (dotOnly: boolean): TaskColumn[] => [
97
+ {
98
+ key: 'slug',
99
+ label: 'Initiative',
100
+ width: TASK_COLUMN_WIDTHS.slug,
101
+ minWidth: TASK_COLUMN_WIDTHS.slug,
102
+ dropPriority: TASK_DROP_ORDER.slug,
103
+ sortable: true,
104
+ },
105
+ {
106
+ key: 'id',
107
+ label: 'ID',
108
+ width: TASK_COLUMN_WIDTHS.id,
109
+ minWidth: TASK_COLUMN_WIDTHS.id,
110
+ sortable: true,
111
+ },
112
+ { key: 'title', label: 'Title', minWidth: TASK_COLUMN_WIDTHS.titleMin, sortable: true },
113
+ SEVERITY_COLUMN[dotOnly ? 'dot' : 'full'],
114
+ {
115
+ key: 'priority',
116
+ label: 'Pri',
117
+ tooltip: 'Priority',
118
+ width: TASK_COLUMN_WIDTHS.priority,
119
+ minWidth: TASK_COLUMN_WIDTHS.priority,
120
+ dropPriority: TASK_DROP_ORDER.priority,
121
+ align: 'right',
122
+ sortable: true,
123
+ },
124
+ {
125
+ key: 'estimate',
126
+ label: 'Est',
127
+ tooltip: 'Estimate',
128
+ width: TASK_COLUMN_WIDTHS.estimate,
129
+ minWidth: TASK_COLUMN_WIDTHS.estimate,
130
+ dropPriority: TASK_DROP_ORDER.estimate,
131
+ align: 'right',
132
+ sortable: true,
133
+ },
134
+ {
135
+ key: 'tags',
136
+ label: 'Tags',
137
+ width: TASK_COLUMN_WIDTHS.tags,
138
+ minWidth: TASK_COLUMN_WIDTHS.tags,
139
+ dropPriority: TASK_DROP_ORDER.tags,
140
+ sortable: false,
141
+ },
142
+ {
143
+ key: 'updated',
144
+ label: 'Age',
145
+ width: TASK_COLUMN_WIDTHS.age,
146
+ minWidth: TASK_COLUMN_WIDTHS.age,
147
+ dropPriority: TASK_DROP_ORDER.updated,
148
+ align: 'right',
149
+ sortable: true,
150
+ },
151
+ ]
152
+
153
+ /**
154
+ * The columns whose order is not their raw field order. Everything else falls
155
+ * through to `useTable`'s default compare.
156
+ */
157
+ const TASK_COMPARATORS: Record<string, TableComparator<TaskListItem>> = {
158
+ // Rank, not alphabet: "critical" < "high" < "low" as strings buries low in the middle.
159
+ severity: (a, b) =>
160
+ severityRank(a.severity) - severityRank(b.severity) || a.priority - b.priority,
161
+ // Newest first when ascending: for an age column, "most recent" is the useful top.
162
+ updated: (a, b) => (a.updated < b.updated ? 1 : a.updated > b.updated ? -1 : 0),
163
+ id: (a, b) => a.id.localeCompare(b.id, undefined, { numeric: true }),
164
+ slug: (a, b) => a.slug.localeCompare(b.slug) || a.priority - b.priority,
165
+ }
166
+
167
+ function SeverityLegend({ tasks }: { tasks: TaskListItem[] }) {
168
+ const counts = useMemo(() => {
169
+ const c: Record<TaskSeverity, number> = { critical: 0, high: 0, medium: 0, low: 0 }
170
+ for (const t of tasks) if (t.severity) c[t.severity]++
171
+ return c
172
+ }, [tasks])
173
+
174
+ return (
175
+ <View className="flex-row items-center gap-3">
176
+ {SEVERITY_ORDER.map((severity) => (
177
+ <View key={severity} className="flex-row items-center gap-1.5">
178
+ <SeverityLabel severity={severity} />
179
+ <Typography variant="mono" className="text-xs text-text-tertiary">
180
+ {String(counts[severity])}
181
+ </Typography>
182
+ </View>
183
+ ))}
184
+ </View>
185
+ )
186
+ }
187
+
188
+ export interface TaskTableProps {
189
+ tasks: TaskListItem[]
190
+ /**
191
+ * Reference timestamp for the age column. Injected rather than read from the
192
+ * clock so stories, tests and visual baselines render identically forever.
193
+ */
194
+ now: number
195
+ /** Column sorted on first render. Defaults to `priority`. */
196
+ defaultSortKey?: TaskSortKey
197
+ /** Hides the severity legend above the grid. */
198
+ hideLegend?: boolean
199
+ /** Severity column mode. Defaults to `auto`, driven by the table's measured width. */
200
+ severityDisplay?: SeverityDisplay
201
+ /** Columns to leave out, for an embedded table that already knows its context. */
202
+ hideColumns?: TaskColumnKey[]
203
+ /**
204
+ * Pins the width the column fit runs against instead of measuring it. For
205
+ * stories and tests that need to hold one step of the drop order still.
206
+ */
207
+ fitWidth?: number
208
+ /** Eyebrow over the grid. Defaults to the backlog wording, `N open · all initiatives`. */
209
+ label?: string
210
+ className?: string
211
+ }
212
+
213
+ /**
214
+ * One measurement drives both adaptations, in the order they should happen:
215
+ * severity collapses to its dot first, and only then do whole columns drop.
216
+ */
217
+ function useFittedColumns(
218
+ display: SeverityDisplay,
219
+ hidden: TaskColumnKey[],
220
+ fitWidth: number | undefined
221
+ ) {
222
+ const { width, onLayout } = useMeasuredWidth(fitWidth)
223
+ const dotOnly =
224
+ display === 'dot' || (display === 'auto' && width !== null && width < COMPACT_SEVERITY_BELOW)
225
+ const candidates = useMemo(() => taskColumns(dotOnly, hidden), [dotOnly, hidden])
226
+ const fit = useColumnFit(candidates, width === null ? null : width - GRID_FRAME_INSET)
227
+
228
+ const columns = useMemo(() => candidates.filter((c) => fit.isVisible(c.key)), [candidates, fit])
229
+ // Rows take the same list the header dropped, so the two halves of a row cannot come apart.
230
+ const rowHidden = useMemo(
231
+ () => [...hidden, ...candidates.filter((c) => !fit.isVisible(c.key)).map((c) => c.key)],
232
+ [hidden, candidates, fit]
233
+ ) as TaskColumnKey[]
234
+
235
+ return { columns, dotOnly, rowHidden, onLayout, contentMinWidth: fit.contentMinWidth }
236
+ }
237
+
238
+ /**
239
+ * TaskTable — every open task across initiatives in one dense, sortable grid.
240
+ *
241
+ * Composes {@link Table} at `density="dense"` and drives it with
242
+ * {@link useTable}, passing per-column comparators so severity ranks by meaning
243
+ * and age reads newest-first. Rows are {@link TaskRow}; the legend tallies
244
+ * {@link SeverityLabel}.
245
+ *
246
+ * Narrow it and it adapts twice, in order: severity collapses to its dot, then
247
+ * whole columns drop along {@link TASK_DROP_ORDER}. `title` and `id` are not in
248
+ * that order and never drop; under them the table scrolls horizontally.
249
+ */
250
+ export function TaskTable({
251
+ tasks,
252
+ now,
253
+ defaultSortKey = 'priority',
254
+ hideLegend = false,
255
+ severityDisplay = 'auto',
256
+ hideColumns = NO_HIDDEN_COLUMNS,
257
+ fitWidth,
258
+ label,
259
+ className,
260
+ }: TaskTableProps) {
261
+ const { columns, dotOnly, rowHidden, onLayout, contentMinWidth } = useFittedColumns(
262
+ severityDisplay,
263
+ hideColumns,
264
+ fitWidth
265
+ )
266
+ const { sortedData, sortColumn, sortDirection, handleSort } = useTable<TaskListItem>({
267
+ data: tasks,
268
+ // One page: this grid is meant to be scanned and scrolled, not paged.
269
+ defaultPageSize: Number.MAX_SAFE_INTEGER,
270
+ defaultSortColumn: defaultSortKey,
271
+ defaultSortDirection: 'asc',
272
+ comparators: TASK_COMPARATORS,
273
+ })
274
+
275
+ return (
276
+ <View className={cn('gap-3', className)} onLayout={onLayout}>
277
+ <View className="flex-row items-center justify-between">
278
+ <Eyebrow>{label ?? `${tasks.length} open · all initiatives`}</Eyebrow>
279
+ {hideLegend ? null : <SeverityLegend tasks={tasks} />}
280
+ </View>
281
+
282
+ {/* A table frame is a hairline RULE, not a plane: it paints no background and casts no lift. */}
283
+ <View className="overflow-hidden rounded-lg border border-hairline" testID="task-grid">
284
+ <Table
285
+ density="dense"
286
+ contentMinWidth={contentMinWidth}
287
+ sortColumn={sortColumn}
288
+ sortDirection={sortDirection}
289
+ onSort={handleSort}
290
+ >
291
+ <TableHeader>
292
+ <TableRow isHoverable={false}>
293
+ {columns.map((col) => (
294
+ <TableHeaderCell
295
+ key={col.key}
296
+ sortKey={col.sortable ? col.key : undefined}
297
+ tooltip={col.tooltip}
298
+ width={col.width}
299
+ align={col.align}
300
+ >
301
+ {col.label}
302
+ </TableHeaderCell>
303
+ ))}
304
+ </TableRow>
305
+ </TableHeader>
306
+ <TableBody>
307
+ {sortedData.map((task) => (
308
+ <TaskRow
309
+ key={`${task.slug}:${task.id}`}
310
+ task={task}
311
+ ageLabel={formatTaskAge(task.updated, now)}
312
+ severityDotOnly={dotOnly}
313
+ hideColumns={rowHidden}
314
+ />
315
+ ))}
316
+ </TableBody>
317
+ </Table>
318
+ </View>
319
+ </View>
320
+ )
321
+ }
@@ -0,0 +1,102 @@
1
+ // The depth contract of the five ActiveWork specimens, asserted against the
2
+ // stories Storybook actually renders — so a reverted decorator fails here too.
3
+ //
4
+ // The planes are asserted as INLINE STYLE on purpose. `Card` writes its
5
+ // background into `style`, so a `bg-*` className on a Card is discarded and only
6
+ // the inline value is real; asserting the class would pass while the render was
7
+ // wrong. That is the exact bug this migration removed. The values come from the
8
+ // ramp so a re-space moves them rather than breaking them.
9
+ import { describe, it, expect } from 'vitest'
10
+ import { render, screen } from '@testing-library/react'
11
+ import { composeStories } from '@storybook/react'
12
+ import { greyRamp } from '../../../theme/tokens/primitives'
13
+ import * as PortfolioStories from './PortfolioOverview.stories'
14
+ import * as TaskTableStories from './TaskTable.stories'
15
+ import * as SessionReaderStories from './SessionReader.stories'
16
+ import * as InitiativeReaderStories from './InitiativeReader.stories'
17
+ import * as ExplorerStories from './FileHistoryExplorer.stories'
18
+
19
+ /** The page plane a `<Surface level="base">` paints. */
20
+ const PAGE = greyRamp[925]
21
+ /** The card plane, two lifts above the page. */
22
+ const CARD = greyRamp[875]
23
+ /** One plane DOWN from a card, where a `CardInset` well sits. */
24
+ const WELL = greyRamp[900]
25
+
26
+ function expectLiftedCard(el: HTMLElement) {
27
+ expect(el).toHaveStyle({ backgroundColor: CARD })
28
+ expect(el.style.boxShadow).toContain('inset 0 1px 0')
29
+ expect(el.style.borderTopWidth).toBe('')
30
+ }
31
+
32
+ function expectPage() {
33
+ expect(screen.getByTestId('page-surface')).toHaveStyle({ backgroundColor: PAGE })
34
+ }
35
+
36
+ describe('T1 · PortfolioOverview depth', () => {
37
+ const { Default } = composeStories(PortfolioStories)
38
+
39
+ it('puts every KPI tile and initiative card on the lifted card plane', () => {
40
+ render(<Default />)
41
+ expectPage()
42
+ for (const el of screen.getAllByTestId('portfolio-stat')) expectLiftedCard(el)
43
+ for (const el of screen.getAllByTestId('initiative-card')) expectLiftedCard(el)
44
+ })
45
+ })
46
+
47
+ describe('T2 · TaskTable depth', () => {
48
+ const { Default } = composeStories(TaskTableStories)
49
+
50
+ it('keeps the grid frame a hairline rule: no plane of its own, no lift', () => {
51
+ render(<Default />)
52
+ expectPage()
53
+ const grid = screen.getByTestId('task-grid')
54
+ expect(grid.style.backgroundColor).toBe('')
55
+ expect(grid.style.boxShadow).toBe('')
56
+ })
57
+ })
58
+
59
+ describe('M1 · Session reader depth', () => {
60
+ const { Default } = composeStories(SessionReaderStories)
61
+
62
+ it('lifts the detail card off the page and leaves the list on it', () => {
63
+ render(<Default />)
64
+ expectPage()
65
+ expectLiftedCard(screen.getByTestId('session-detail'))
66
+ })
67
+ })
68
+
69
+ describe('M2 · Initiative reader depth', () => {
70
+ const { Default } = composeStories(InitiativeReaderStories)
71
+
72
+ it('lifts all three reader cards onto the card plane', () => {
73
+ render(<Default />)
74
+ expectPage()
75
+ const cards = screen.getAllByTestId('reader-card')
76
+ expect(cards).toHaveLength(3)
77
+ for (const el of cards) expectLiftedCard(el)
78
+ })
79
+ })
80
+
81
+ describe('F1 · FileHistoryExplorer depth', () => {
82
+ const { Default } = composeStories(ExplorerStories)
83
+
84
+ it('lifts both panes and sinks the growth well one plane inside the detail', () => {
85
+ render(<Default />)
86
+ expectPage()
87
+ expectLiftedCard(screen.getByTestId('file-list-pane'))
88
+ expectLiftedCard(screen.getByTestId('file-activity-detail'))
89
+
90
+ const well = screen.getByTestId('growth-well')
91
+ expect(well).toHaveStyle({ backgroundColor: WELL })
92
+ expect(well.style.boxShadow).toContain('inset')
93
+ expect(well.style.boxShadow).not.toContain('inset 0 1px 0 rgba(255,255,255')
94
+ })
95
+
96
+ it('keeps co-change chips tone-only, so only the panes lift', () => {
97
+ render(<Default />)
98
+ const chip = screen.getAllByTestId('co-change-chip')[0]
99
+ expect(chip).toHaveStyle({ backgroundColor: CARD })
100
+ expect(chip.style.boxShadow).toBe('')
101
+ })
102
+ })
@@ -0,0 +1,91 @@
1
+ import type { FileActivityDetailData } from './FileActivityDetail'
2
+ import type { CoChangeEdge, FileHistoryStat } from './FileHistoryExplorer'
3
+
4
+ /**
5
+ * A small, hand-trimmed slice of a real session-history mine, used by the
6
+ * ActiveWork file-history stories and tests. Fixed values (no `Date.now()`, no
7
+ * randomness) so visual baselines stay deterministic.
8
+ */
9
+ export const FILE_HISTORY_FILES: FileActivityDetailData[] = [
10
+ {
11
+ path: 'src/commands/open.ts',
12
+ reads: 84,
13
+ writes: 12,
14
+ edits: 61,
15
+ touches: 157,
16
+ sessions: 23,
17
+ charsAdded: 41280,
18
+ charsRemoved: 9640,
19
+ netGrowth: 31640,
20
+ firstTouched: '2026-05-02T10:12:00Z',
21
+ lastTouched: '2026-07-11T18:40:00Z',
22
+ timeline: [120, 340, -80, 910, 420, 0, 1580, 260, -310, 740, 1120, 90],
23
+ coChange: [
24
+ { path: 'src/commands/_open-helpers.ts', count: 19 },
25
+ { path: 'src/registry/index.ts', count: 11 },
26
+ { path: '__tests__/commands/open.test.ts', count: 8 },
27
+ ],
28
+ },
29
+ {
30
+ path: 'src/bootstrap/prompt.ts',
31
+ reads: 71,
32
+ writes: 9,
33
+ edits: 44,
34
+ touches: 124,
35
+ sessions: 19,
36
+ charsAdded: 33900,
37
+ charsRemoved: 15220,
38
+ netGrowth: 18680,
39
+ firstTouched: '2026-05-04T09:00:00Z',
40
+ lastTouched: '2026-07-10T21:05:00Z',
41
+ timeline: [80, 610, 1240, -420, 300, 150, -90, 880, 410, 220],
42
+ coChange: [
43
+ { path: 'src/commands/open.ts', count: 14 },
44
+ { path: '__tests__/bootstrap/prompt.test.ts', count: 9 },
45
+ ],
46
+ },
47
+ {
48
+ path: 'src/utils/fs-atomic.ts',
49
+ reads: 52,
50
+ writes: 4,
51
+ edits: 18,
52
+ touches: 74,
53
+ sessions: 12,
54
+ charsAdded: 8110,
55
+ charsRemoved: 11480,
56
+ netGrowth: -3370,
57
+ firstTouched: '2026-05-09T14:30:00Z',
58
+ lastTouched: '2026-06-28T11:15:00Z',
59
+ timeline: [420, -180, -960, 240, -510, 130, -320, 80],
60
+ coChange: [{ path: 'src/utils/flock.ts', count: 7 }],
61
+ },
62
+ {
63
+ path: 'README.md',
64
+ reads: 40,
65
+ writes: 6,
66
+ edits: 9,
67
+ touches: 55,
68
+ sessions: 8,
69
+ charsAdded: 6200,
70
+ charsRemoved: 1450,
71
+ netGrowth: 4750,
72
+ firstTouched: '2026-05-01T08:00:00Z',
73
+ lastTouched: '2026-07-02T16:20:00Z',
74
+ timeline: [1200, 0, 340, 0, 0, 890, 120],
75
+ coChange: [],
76
+ },
77
+ ]
78
+
79
+ export const FILE_HISTORY_CO_EDGES: CoChangeEdge[] = [
80
+ { a: 'src/commands/open.ts', b: 'src/commands/_open-helpers.ts', count: 19 },
81
+ { a: 'src/commands/open.ts', b: 'src/bootstrap/prompt.ts', count: 14 },
82
+ { a: 'src/bootstrap/prompt.ts', b: '__tests__/bootstrap/prompt.test.ts', count: 9 },
83
+ { a: 'src/utils/fs-atomic.ts', b: 'src/utils/flock.ts', count: 7 },
84
+ ]
85
+
86
+ export const FILE_HISTORY_STATS: FileHistoryStat[] = [
87
+ { label: 'Files', value: '4' },
88
+ { label: 'File events', value: '410' },
89
+ { label: 'Sessions', value: '23' },
90
+ { label: 'Transcripts', value: '31' },
91
+ ]
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { formatSessionDuration, formatTaskAge } from './format-time'
3
+
4
+ describe('formatSessionDuration', () => {
5
+ it('reads minutes under an hour and hours plus minutes above it', () => {
6
+ expect(formatSessionDuration('2026-07-01T10:00:00Z', '2026-07-01T10:42:00Z')).toBe('42m')
7
+ expect(formatSessionDuration('2026-07-01T10:00:00Z', '2026-07-01T11:04:00Z')).toBe('1h 4m')
8
+ })
9
+
10
+ it('is empty for a missing, reversed or unparseable span so callers can drop the separator', () => {
11
+ expect(formatSessionDuration('2026-07-01T11:00:00Z', '2026-07-01T10:00:00Z')).toBe('')
12
+ expect(formatSessionDuration('nope', '2026-07-01T10:00:00Z')).toBe('')
13
+ })
14
+ })
15
+
16
+ describe('formatTaskAge (shared with the task table)', () => {
17
+ const now = new Date('2026-07-13T09:00:00Z').getTime()
18
+ it('still reads today, days and months', () => {
19
+ expect(formatTaskAge('2026-07-13T01:00:00Z', now)).toBe('today')
20
+ expect(formatTaskAge('2026-07-01T01:00:00Z', now)).toBe('12d ago')
21
+ expect(formatTaskAge('2026-05-13T01:00:00Z', now)).toBe('2mo ago')
22
+ })
23
+ })
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A compact age label for a dense column: `today`, `4d ago`, `3mo ago`.
3
+ *
4
+ * Deliberately not `DateTime format="relative"` — that renders Intl prose ("4
5
+ * days ago"), which is too long for a 74px column, and it reads `Date.now()`
6
+ * internally so a story or a visual baseline could never be deterministic. `now`
7
+ * is injected here for exactly that reason.
8
+ */
9
+ export function formatTaskAge(iso: string | null | undefined, now: number): string {
10
+ if (!iso) return '—'
11
+ const then = new Date(iso).getTime()
12
+ if (Number.isNaN(then)) return '—'
13
+
14
+ const days = Math.floor((now - then) / 86_400_000)
15
+ if (days <= 0) return 'today'
16
+ if (days === 1) return '1d ago'
17
+ if (days < 30) return `${days}d ago`
18
+ return `${Math.floor(days / 30)}mo ago`
19
+ }
20
+
21
+ /**
22
+ * A session's wall-clock length as `1h 4m` or `42m`. Empty when the span is
23
+ * missing, unparseable or not positive, so callers can drop the separator
24
+ * rather than render `0m`.
25
+ */
26
+ export function formatSessionDuration(started: string, ended: string): string {
27
+ const ms = new Date(ended).getTime() - new Date(started).getTime()
28
+ if (!(ms > 0)) return ''
29
+ const minutes = Math.round(ms / 60_000)
30
+ const hours = Math.floor(minutes / 60)
31
+ return hours ? `${hours}h ${minutes % 60}m` : `${minutes}m`
32
+ }
@@ -0,0 +1,19 @@
1
+ export * from './Eyebrow'
2
+ export * from './InitiativeCard'
3
+ export * from './PortfolioOverview'
4
+ export * from './FilePathLabel'
5
+ export * from './FileActivityRow'
6
+ export * from './FileActivityDetail'
7
+ export * from './CoChangeChip'
8
+ export * from './FileHistoryExplorer'
9
+ export * from './SeverityLabel'
10
+ export * from './TaskRow'
11
+ export * from './TaskTable'
12
+ export * from './format-time'
13
+ export * from './session-linkers'
14
+ export * from './SessionListItem'
15
+ export * from './SessionList'
16
+ export * from './SessionDetail'
17
+ export * from './InitiativeHeader'
18
+ export * from './InitiativeBrief'
19
+ export * from './OpenLoops'