@titan-design/react-ui 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -47
- package/dist/bodymap.js +768 -701
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +770 -703
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1319 -216
- package/dist/index.d.ts +1319 -216
- package/dist/index.js +5996 -4498
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5773 -4357
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-CvsFqNtx.d.mts → pages-CpW3OB3g.d.mts} +21 -3
- package/dist/{pages-B1hqnGIC.d.ts → pages-_3Ajpiqu.d.ts} +21 -3
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1435 -1428
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1443 -1436
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +195 -134
- package/dist/theme/index.d.ts +195 -134
- package/dist/theme/index.js +215 -300
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +202 -296
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +62 -5
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +62 -5
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +73 -5
- package/dist/theme/tokens.d.ts +73 -5
- package/dist/theme/tokens.js +55 -7
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +55 -8
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/audits/2026-09-08-critique-addenda.md +51 -0
- package/docs/audits/2026-09-08-library-critique.md +212 -0
- package/docs/library-roadmap.md +99 -0
- package/package.json +1 -1
- package/src/arch/arch-graph.freshness.test.ts +52 -0
- package/src/arch/arch-graph.json +5021 -1617
- package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
- package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
- package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
- package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
- package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
- package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
- package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
- package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
- package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
- package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
- package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
- package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +181 -0
- package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
- package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
- package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
- package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
- package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
- package/src/components/custom/ActiveWork/README.md +305 -0
- package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
- package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
- package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
- package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
- package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
- package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
- package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
- package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
- package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
- package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
- package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
- package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
- package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
- package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
- package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
- package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
- package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
- package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
- package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
- package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
- package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
- package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
- package/src/components/custom/ActiveWork/format-time.ts +32 -0
- package/src/components/custom/ActiveWork/index.ts +19 -0
- package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
- package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
- package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
- package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +4 -3
- package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
- package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
- package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
- package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
- package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
- package/src/components/custom/Fatigue/README.md +40 -3
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
- package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
- package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
- package/src/components/custom/Fatigue/index.ts +17 -5
- package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
- package/src/components/custom/Fatigue/panel-layout.ts +185 -0
- package/src/components/custom/Gauge/Gauge.stories.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.tsx +4 -1
- package/src/components/custom/Metric/Metric.stories.tsx +10 -1
- package/src/components/custom/Metric/Metric.test.tsx +1 -3
- package/src/components/custom/Metric/Metric.tsx +5 -20
- package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
- package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
- package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
- package/src/components/custom/Prose/README.md +58 -0
- package/src/components/custom/Prose/index.ts +1 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +46 -8
- package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
- package/src/components/custom/Scatter/Scatter.tsx +116 -14
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +40 -7
- package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
- package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
- package/src/components/custom/Table/Table.stories.tsx +98 -15
- package/src/components/custom/Table/Table.test.tsx +273 -86
- package/src/components/custom/Table/Table.tsx +159 -67
- package/src/components/custom/Table/column-fit.test.ts +75 -0
- package/src/components/custom/Table/column-fit.ts +116 -0
- package/src/components/custom/Table/index.ts +9 -0
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
- package/src/components/custom/Treemap/Treemap.stories.tsx +2 -2
- package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +7 -2
- package/src/components/custom/Typography/Typography.stories.tsx +28 -1
- package/src/components/custom/Typography/Typography.test.tsx +34 -0
- package/src/components/custom/Typography/Typography.tsx +6 -0
- package/src/components/custom/Typography/index.ts +1 -8
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +9 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +19 -7
- package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
- package/src/components/custom/Workout/BaseBadge.tsx +3 -0
- package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
- package/src/components/custom/Workout/BodyMap.tsx +3 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +16 -4
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +45 -9
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +10 -8
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
- package/src/components/custom/Workout/CapacityBandChart.tsx +139 -108
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
- package/src/components/custom/Workout/DeviationBar.tsx +21 -16
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +182 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +3 -2
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -7
- package/src/components/custom/Workout/ExerciseCard.test.tsx +13 -3
- package/src/components/custom/Workout/ExerciseCard.tsx +42 -51
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +7 -7
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseHeading.tsx +8 -5
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -4
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
- package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
- package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
- package/src/components/custom/Workout/IntensityBar.tsx +4 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
- package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
- package/src/components/custom/Workout/MesoCard.tsx +12 -9
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
- package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
- package/src/components/custom/Workout/MesoStatusCard.tsx +9 -23
- package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
- package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
- package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
- package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
- package/src/components/custom/Workout/PrBadge.tsx +13 -17
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +4 -1
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
- package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
- package/src/components/custom/Workout/README.md +11 -6
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
- package/src/components/custom/Workout/ReadinessCheck.tsx +19 -3
- package/src/components/custom/Workout/RestTimer.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
- package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SegmentedBar.tsx +6 -2
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SessionHeader.tsx +12 -12
- package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
- package/src/components/custom/Workout/SessionRail.tsx +7 -9
- package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
- package/src/components/custom/Workout/SetBar.test.tsx +49 -4
- package/src/components/custom/Workout/SetBar.tsx +38 -12
- package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
- package/src/components/custom/Workout/SetRow.test.tsx +16 -2
- package/src/components/custom/Workout/SetRow.tsx +61 -23
- package/src/components/custom/Workout/SetStrip.stories.tsx +60 -5
- package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
- package/src/components/custom/Workout/SetStrip.tsx +24 -4
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
- package/src/components/custom/Workout/SetsRepsLoad.tsx +3 -5
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
- package/src/components/custom/Workout/Sparkline.tsx +14 -18
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
- package/src/components/custom/Workout/StatusDot.tsx +93 -63
- package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
- package/src/components/custom/Workout/StatusPill.tsx +27 -38
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +9 -3
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
- package/src/components/custom/Workout/StrengthTrendChart.tsx +31 -30
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
- package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +22 -7
- package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +7 -4
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +3 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
- package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
- package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
- package/src/components/custom/Workout/WeekRow.tsx +23 -16
- package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
- package/src/components/custom/Workout/WeightBadge.tsx +28 -27
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
- package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
- package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +7 -6
- package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
- package/src/components/custom/Workout/index.ts +5 -0
- package/src/components/custom/Workout/metricText.stories.tsx +6 -5
- package/src/components/custom/Workout/metricText.tsx +4 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +207 -26
- package/src/components/custom/charts/README.md +61 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +30 -1
- package/src/components/custom/charts/SetBarChart.tsx +42 -4
- package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
- package/src/components/custom/charts/SparkBars.test.tsx +69 -0
- package/src/components/custom/charts/SparkBars.tsx +85 -0
- package/src/components/custom/charts/index.ts +3 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
- package/src/components/custom/stepper/Stepper.test.tsx +1 -7
- package/src/components/custom/stepper/Stepper.tsx +13 -28
- package/src/components/icons/icons.stories.tsx +1 -1
- package/src/components/icons/icons.tsx +51 -0
- package/src/components/shell/AppShell.stories.tsx +150 -0
- package/src/components/shell/AppShell.test.tsx +70 -0
- package/src/components/shell/AppShell.tsx +90 -0
- package/src/components/shell/BrandLockup.stories.tsx +66 -5
- package/src/components/shell/BrandLockup.test.tsx +34 -1
- package/src/components/shell/BrandLockup.tsx +27 -11
- package/src/components/shell/NavItem.stories.tsx +4 -4
- package/src/components/shell/NavItem.test.tsx +17 -0
- package/src/components/shell/NavItem.tsx +17 -5
- package/src/components/shell/README.md +89 -26
- package/src/components/shell/SideNav.stories.tsx +38 -7
- package/src/components/shell/SideNav.test.tsx +16 -8
- package/src/components/shell/SideNav.tsx +17 -17
- package/src/components/shell/TopBar.stories.tsx +44 -19
- package/src/components/shell/TopBar.test.tsx +34 -12
- package/src/components/shell/TopBar.tsx +61 -40
- package/src/components/shell/brands.tsx +66 -0
- package/src/components/shell/index.ts +9 -8
- package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
- package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
- package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
- package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
- package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
- package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
- package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
- package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
- package/src/components/shell/workout/WorkoutShell.tsx +77 -0
- package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
- package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
- package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
- package/src/components/shell/workout/index.ts +9 -0
- package/src/components/shell/workout/workoutNavItems.tsx +16 -0
- package/src/components/ui/README.md +110 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
- package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -10
- package/src/components/ui/badge/Badge.stories.tsx +83 -28
- package/src/components/ui/badge/Badge.test.tsx +19 -3
- package/src/components/ui/badge/Badge.tsx +48 -55
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
- package/src/components/ui/button/Button.stories.tsx +46 -12
- package/src/components/ui/button/Button.tsx +32 -25
- package/src/components/ui/card/Card.stories.tsx +90 -94
- package/src/components/ui/card/Card.test.tsx +80 -6
- package/src/components/ui/card/Card.tsx +150 -203
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +6 -26
- package/src/components/ui/chip/Chip.stories.tsx +37 -13
- package/src/components/ui/chip/Chip.test.tsx +10 -6
- package/src/components/ui/chip/Chip.tsx +76 -91
- package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
- package/src/components/ui/collapse/Collapse.tsx +6 -11
- package/src/components/ui/data-row/DataRow.stories.tsx +9 -2
- package/src/components/ui/data-row/DataRow.test.tsx +5 -15
- package/src/components/ui/data-row/DataRow.tsx +18 -7
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.tsx +1 -5
- package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
- package/src/components/ui/drawer/Drawer.tsx +13 -30
- package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
- package/src/components/ui/form-field/FormField.stories.tsx +6 -20
- package/src/components/ui/form-field/FormField.test.tsx +1 -5
- package/src/components/ui/form-field/FormField.tsx +10 -46
- package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
- package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
- package/src/components/ui/help-tip/HelpTip.tsx +21 -22
- package/src/components/ui/help-tip/index.ts +7 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
- package/src/components/ui/icon-box/IconBox.tsx +2 -12
- package/src/components/ui/indicator/Indicator.stories.tsx +1 -1
- package/src/components/ui/indicator/Indicator.tsx +20 -14
- package/src/components/ui/input/Input.stories.tsx +3 -11
- package/src/components/ui/input/Input.test.tsx +2 -10
- package/src/components/ui/input/Input.tsx +10 -25
- package/src/components/ui/input/index.ts +7 -1
- package/src/components/ui/link/Link.stories.tsx +19 -7
- package/src/components/ui/list-item/ListItem.stories.tsx +11 -2
- package/src/components/ui/list-item/ListItem.tsx +2 -9
- package/src/components/ui/menu/Menu.stories.tsx +21 -15
- package/src/components/ui/menu/Menu.test.tsx +2 -11
- package/src/components/ui/menu/Menu.tsx +12 -14
- package/src/components/ui/menu/index.ts +8 -1
- package/src/components/ui/modal/Modal.stories.tsx +2 -2
- package/src/components/ui/modal/Modal.tsx +29 -19
- package/src/components/ui/pill/Pill.stories.tsx +58 -23
- package/src/components/ui/pill/Pill.test.tsx +101 -3
- package/src/components/ui/pill/Pill.tsx +164 -49
- package/src/components/ui/pill/index.ts +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +5 -7
- package/src/components/ui/popover/Popover.test.tsx +1 -6
- package/src/components/ui/popover/Popover.tsx +20 -15
- package/src/components/ui/popover/index.ts +7 -1
- package/src/components/ui/progress/Progress.stories.tsx +3 -12
- package/src/components/ui/progress/Progress.test.tsx +1 -5
- package/src/components/ui/radio/Radio.stories.tsx +42 -21
- package/src/components/ui/radio/Radio.test.tsx +6 -2
- package/src/components/ui/radio/Radio.tsx +23 -22
- package/src/components/ui/section/Section.stories.tsx +5 -14
- package/src/components/ui/section/Section.test.tsx +1 -6
- package/src/components/ui/section/Section.tsx +1 -3
- package/src/components/ui/section/index.ts +1 -5
- package/src/components/ui/select/Select.stories.tsx +3 -13
- package/src/components/ui/select/Select.test.tsx +2 -11
- package/src/components/ui/select/Select.tsx +14 -14
- package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
- package/src/components/ui/skeleton/Skeleton.tsx +3 -14
- package/src/components/ui/skeleton/index.ts +1 -7
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +1 -4
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +17 -5
- package/src/components/ui/surface/Surface.test.tsx +88 -28
- package/src/components/ui/surface/Surface.tsx +37 -87
- package/src/components/ui/surface/SurfaceContext.ts +22 -62
- package/src/components/ui/surface/index.ts +8 -0
- package/src/components/ui/surface/resolveSurface.ts +98 -0
- package/src/components/ui/surface/surface.contract.test.ts +6 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.tsx +7 -6
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +7 -22
- package/src/components/ui/tabs/index.ts +1 -7
- package/src/components/ui/tile/Tile.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.tsx +3 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toast/Toast.test.tsx +2 -8
- package/src/components/ui/toast/Toast.tsx +43 -53
- package/src/components/ui/toast/index.ts +0 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +25 -43
- package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
- package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
- package/src/components/ui/tooltip/Tooltip.tsx +55 -36
- package/src/components/ui/tooltip/index.ts +1 -1
- package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
- package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
- package/src/components/ui/trigger/index.ts +2 -0
- package/src/stories/PresetShowcase.stories.tsx +100 -47
- package/src/test/no-device-internals.test.ts +3 -3
- package/src/theme/ChoosingTokens.stories.tsx +48 -0
- package/src/theme/ColorPalettes.stories.tsx +62 -45
- package/src/theme/ColorPrimitives.stories.tsx +35 -35
- package/src/theme/Depth.stories.tsx +132 -65
- package/src/theme/DepthCalibration.stories.tsx +23 -21
- package/src/theme/ThemeProvider.test.tsx +4 -4
- package/src/theme/barrel.ts +7 -6
- package/src/theme/color-stories.coverage.test.ts +1 -1
- package/src/theme/color-utils.ts +41 -29
- package/src/theme/config.completeness.test.ts +1 -1
- package/src/theme/config.ts +28 -0
- package/src/theme/elevation.test.ts +82 -56
- package/src/theme/elevation.ts +77 -561
- package/src/theme/global.css +30 -2
- package/src/theme/grey-ramp.test.ts +24 -8
- package/src/theme/lift.test.ts +39 -0
- package/src/theme/lift.ts +102 -0
- package/src/theme/materials.test.ts +5 -2
- package/src/theme/materials.ts +38 -4
- package/src/theme/native-theming-guard.test.ts +5 -5
- package/src/theme/presets/apply.ts +4 -0
- package/src/theme/presets/audiobook.ts +5 -2
- package/src/theme/presets/default.ts +4 -0
- package/src/theme/presets/index.ts +14 -1
- package/src/theme/presets/types.ts +1 -1
- package/src/theme/semantic-contrast.test.ts +13 -4
- package/src/theme/status-distinctness.test.ts +66 -0
- package/src/theme/surface-planes.test.ts +37 -0
- package/src/theme/surface-planes.ts +68 -0
- package/src/theme/token-export-guard.test.ts +1 -3
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +65 -33
- package/src/theme/tokens/semantic.ts +71 -37
- package/src/theme/wash-ladder.test.ts +58 -0
- package/src/theme/workout-tokens.ts +1 -7
- package/src/types/jest-axe.d.ts +2 -6
- package/src/types/raw-md.d.ts +5 -0
- package/src/utils/colors.ts +4 -4
- package/src/utils/form.ts +1 -3
- package/src/utils/index.ts +2 -7
- package/src/utils/number-format.ts +17 -0
- package/src/utils/workout-format.ts +15 -6
- package/src/web-jsx/jsx-runtime.ts +1 -5
- package/tailwind.config.js +5 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
- package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
- package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
- package/src/components/shell/DashboardShell.stories.tsx +0 -65
- package/src/components/shell/DashboardShell.tsx +0 -75
- package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
- package/src/theme/elevation.stories.tsx +0 -346
- package/src/utils/useTheme.ts +0 -72
- /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
- /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
- /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
- /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'
|