@titan-design/react-ui 0.12.0 → 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 +1320 -215
- package/dist/index.d.ts +1320 -215
- 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 +202 -1042
- package/dist/theme/index.d.ts +202 -1042
- 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 +972 -0
- package/dist/theme/tokens.d.ts +972 -0
- package/dist/theme/tokens.js +904 -0
- package/dist/theme/tokens.js.map +1 -0
- package/dist/theme/tokens.mjs +880 -0
- package/dist/theme/tokens.mjs.map +1 -0
- 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 +7 -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 +12 -7
- 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 +90 -27
- 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 +24 -22
- 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 +102 -0
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +67 -35
- 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
|
@@ -2,32 +2,33 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* LiveFatiguePanel — the aligned "Live panel v2": the loss-relative {@link VelocityHero}
|
|
4
4
|
* (primary) beside the vertical {@link LiveFatigueCard} (secondary), flooded by a
|
|
5
|
-
* coaching {@link LiveAuraFrame} whose category tracks the verdict state.
|
|
6
|
-
* lite exercise header sits above.
|
|
5
|
+
* coaching {@link LiveAuraFrame} whose category tracks the verdict state.
|
|
7
6
|
*
|
|
8
7
|
* The velocity hero's per-rep velocities are NOT on the fatigue model (they come from
|
|
9
8
|
* the live-view velocity path), so they're passed as their own `velocity` prop.
|
|
9
|
+
*
|
|
10
|
+
* SURFACE (TD-03.59). Colour resolves from the enclosing `<Surface>` through
|
|
11
|
+
* {@link useOnSurfaceColor}, not from a module-scope `getSemanticColors('dark')`. Outside
|
|
12
|
+
* any Surface the context still defaults to the dark `base` plane, so the wall display
|
|
13
|
+
* renders exactly as before while a light surface now works.
|
|
14
|
+
*
|
|
15
|
+
* RESPONSIVE (TD-03.58). The panel is CONTAINER-responsive (SIZE-D01), not prop-sized: it
|
|
16
|
+
* measures its own width in `onLayout` and hands it to {@link panelLayout}, which owns the
|
|
17
|
+
* stack/expand tiers, the spacing and the card width. The one `bodyHeight` is split across
|
|
18
|
+
* the hero and the card by {@link panelBodySplit} — one call, so the two cannot disagree.
|
|
19
|
+
* Nothing here animates: a tier change is a re-layout, never a transition, because the panel
|
|
20
|
+
* is read from across a room mid-set.
|
|
10
21
|
*/
|
|
11
|
-
import {
|
|
22
|
+
import { useState } from 'react'
|
|
23
|
+
import { View, Text, type LayoutChangeEvent } from 'react-native'
|
|
12
24
|
import { LiveAuraFrame, type LiveAuraCategory } from '../Workout/LiveAuraFrame'
|
|
13
|
-
import {
|
|
14
|
-
import { alpha } from '../../../utils/colors'
|
|
25
|
+
import { useOnSurfaceColor } from '../../ui/surface'
|
|
15
26
|
import { VelocityHero } from './VelocityHero'
|
|
16
27
|
import { LiveFatigueCard } from './LiveFatigueCard'
|
|
17
|
-
import {
|
|
28
|
+
import { FONT_MONO, auraForVerdict } from './fatigue-tokens'
|
|
29
|
+
import { panelLayout, panelBodySplit } from './panel-layout'
|
|
18
30
|
import type { LiveFatigueModel } from './fatigue-model'
|
|
19
31
|
|
|
20
|
-
const t = getSemanticColors('dark')
|
|
21
|
-
|
|
22
|
-
export interface LiveFatiguePanelHeader {
|
|
23
|
-
/** Exercise name — the large title. */
|
|
24
|
-
title: string
|
|
25
|
-
/** Prescription / block line under the title. */
|
|
26
|
-
subtitle?: string
|
|
27
|
-
/** Right-aligned progress meta, e.g. "SET 3 · REP 7 / 8". */
|
|
28
|
-
meta?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
32
|
export interface LiveFatiguePanelVelocity {
|
|
32
33
|
/** Per-rep MEAN concentric velocity (m/s), ordered by rep. */
|
|
33
34
|
velocities: number[]
|
|
@@ -40,97 +41,63 @@ export interface LiveFatiguePanelProps {
|
|
|
40
41
|
model: LiveFatigueModel
|
|
41
42
|
/** The velocity-hero data (its own source — not on the fatigue model). */
|
|
42
43
|
velocity: LiveFatiguePanelVelocity
|
|
43
|
-
/** Optional lite exercise header. Omitted → no header row. */
|
|
44
|
-
header?: LiveFatiguePanelHeader
|
|
45
44
|
/** Aura-flood category. Defaults to the verdict-derived category. */
|
|
46
45
|
aura?: LiveAuraCategory
|
|
47
46
|
/** Panel body height in px. Default 508. */
|
|
48
47
|
bodyHeight?: number
|
|
49
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Pin the fatigue-card column width instead of letting the tier choose it. Ignored when
|
|
50
|
+
* the panel is stacked (the card fills the content width there).
|
|
51
|
+
*/
|
|
50
52
|
cardWidth?: number
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
flexDirection: 'row',
|
|
58
|
-
alignItems: 'flex-end',
|
|
59
|
-
justifyContent: 'space-between',
|
|
60
|
-
paddingHorizontal: 24,
|
|
61
|
-
paddingTop: 18,
|
|
62
|
-
paddingBottom: 14,
|
|
63
|
-
borderBottomWidth: 1,
|
|
64
|
-
borderColor: alpha(t['text-primary'], 0.08),
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
<View style={{ gap: 2 }}>
|
|
68
|
-
<Text
|
|
69
|
-
style={{
|
|
70
|
-
fontSize: 28,
|
|
71
|
-
fontWeight: '700',
|
|
72
|
-
fontFamily: FONT_HEAD,
|
|
73
|
-
color: t['text-primary'],
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
{header.title}
|
|
77
|
-
</Text>
|
|
78
|
-
{header.subtitle && (
|
|
79
|
-
<Text
|
|
80
|
-
style={{
|
|
81
|
-
fontSize: 13,
|
|
82
|
-
fontWeight: '600',
|
|
83
|
-
fontFamily: FONT_UI,
|
|
84
|
-
color: t['text-secondary'],
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
{header.subtitle}
|
|
88
|
-
</Text>
|
|
89
|
-
)}
|
|
90
|
-
</View>
|
|
91
|
-
{header.meta && (
|
|
92
|
-
<Text
|
|
93
|
-
style={{
|
|
94
|
-
fontSize: 13,
|
|
95
|
-
fontWeight: '800',
|
|
96
|
-
fontFamily: FONT_MONO,
|
|
97
|
-
color: t['text-tertiary'],
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
{header.meta}
|
|
101
|
-
</Text>
|
|
102
|
-
)}
|
|
103
|
-
</View>
|
|
104
|
-
)
|
|
53
|
+
/**
|
|
54
|
+
* Override the measured container width, in px. The panel measures itself, so this is
|
|
55
|
+
* for tests (`onLayout` never fires under jsdom) and for a consumer that already knows
|
|
56
|
+
* the width it is about to hand the panel.
|
|
57
|
+
*/
|
|
58
|
+
containerWidth?: number
|
|
105
59
|
}
|
|
106
60
|
|
|
107
61
|
export function LiveFatiguePanel({
|
|
108
62
|
model,
|
|
109
63
|
velocity,
|
|
110
|
-
header,
|
|
111
64
|
aura,
|
|
112
65
|
bodyHeight = 508,
|
|
113
|
-
cardWidth
|
|
66
|
+
cardWidth,
|
|
67
|
+
containerWidth,
|
|
114
68
|
}: LiveFatiguePanelProps) {
|
|
69
|
+
const eyebrowColor = useOnSurfaceColor('tertiary')
|
|
70
|
+
const [measuredWidth, setMeasuredWidth] = useState(0)
|
|
71
|
+
const width = containerWidth ?? measuredWidth
|
|
72
|
+
const layout = panelLayout(width)
|
|
73
|
+
const { heroHeight, cardHeight } = panelBodySplit(bodyHeight, layout)
|
|
115
74
|
const category = aura ?? auraForVerdict(model.verdict?.state ?? null)
|
|
116
|
-
const heroH = bodyHeight - 26 // leaves room for the hero's own eyebrow above it
|
|
117
75
|
return (
|
|
118
76
|
<LiveAuraFrame
|
|
119
77
|
category={category}
|
|
120
78
|
style={{ borderRadius: 0, borderWidth: 0 }}
|
|
121
79
|
testID="live-fatigue-panel"
|
|
80
|
+
onLayout={(e: LayoutChangeEvent) => setMeasuredWidth(e.nativeEvent.layout.width)}
|
|
122
81
|
>
|
|
123
82
|
<View style={{ flex: 1 }}>
|
|
124
|
-
|
|
125
|
-
|
|
83
|
+
<View
|
|
84
|
+
testID="live-fatigue-body"
|
|
85
|
+
style={{
|
|
86
|
+
padding: layout.padding,
|
|
87
|
+
flexDirection: layout.stacked ? 'column' : 'row',
|
|
88
|
+
gap: layout.gap,
|
|
89
|
+
alignItems: 'stretch',
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
126
92
|
{/* PRIMARY — the velocity hero with VL bands; flexes to fill the width the card leaves. */}
|
|
127
93
|
<View style={{ flex: 1, gap: 8 }}>
|
|
128
94
|
<Text
|
|
95
|
+
testID="live-fatigue-eyebrow"
|
|
129
96
|
style={{
|
|
130
97
|
fontSize: 9,
|
|
131
98
|
letterSpacing: 1.2,
|
|
132
99
|
fontFamily: FONT_MONO,
|
|
133
|
-
color:
|
|
100
|
+
color: eyebrowColor,
|
|
134
101
|
}}
|
|
135
102
|
>
|
|
136
103
|
VELOCITY · this set
|
|
@@ -139,11 +106,15 @@ export function LiveFatiguePanel({
|
|
|
139
106
|
velocities={velocity.velocities}
|
|
140
107
|
targetReps={velocity.targetReps}
|
|
141
108
|
liveRepIndex={velocity.liveRepIndex}
|
|
142
|
-
height={
|
|
109
|
+
height={heroHeight}
|
|
143
110
|
/>
|
|
144
111
|
</View>
|
|
145
|
-
{/* SECONDARY — the vertical fatigue card. */}
|
|
146
|
-
<LiveFatigueCard
|
|
112
|
+
{/* SECONDARY — the vertical fatigue card. Stacked, it fills the content width. */}
|
|
113
|
+
<LiveFatigueCard
|
|
114
|
+
model={model}
|
|
115
|
+
width={cardWidth ?? layout.cardWidth}
|
|
116
|
+
height={cardHeight}
|
|
117
|
+
/>
|
|
147
118
|
</View>
|
|
148
119
|
</View>
|
|
149
120
|
</LiveAuraFrame>
|
|
@@ -17,7 +17,7 @@ LiveFatiguePanel ← the composition (Live panel v2)
|
|
|
17
17
|
└─ LiveFatigueCard ← secondary read (consumes LiveFatigueModel)
|
|
18
18
|
├─ VerdictHero (RPE number + verdict word, tone-flooded)
|
|
19
19
|
├─ FatigueLights (VEL/ROM/TEMPO "why" dots)
|
|
20
|
-
│ ├─
|
|
20
|
+
│ ├─ Indicator (ui/indicator — the dot primitive, glow)
|
|
21
21
|
│ └─ Tooltip (ui/tooltip — hover detail)
|
|
22
22
|
├─ RomProgressionChart (per-rep silver/red depth bars + reference lines)
|
|
23
23
|
├─ GhostSpark (per-rep velocity-time sparkline; tempo EMBEDDED)
|
|
@@ -38,8 +38,11 @@ LiveFatiguePanel ← the composition (Live panel v2)
|
|
|
38
38
|
|
|
39
39
|
## Reuse audit — composed, not hand-rolled
|
|
40
40
|
|
|
41
|
-
- **`
|
|
42
|
-
inside a `Tooltip`; no re-implemented dot.
|
|
41
|
+
- **`Indicator`** (ui/indicator/) — the fatigue lights are an `Indicator` (glow) + a mono
|
|
42
|
+
label inside a `Tooltip`; no re-implemented dot. Roadmap decision 10 settled the two
|
|
43
|
+
competing dots in `Indicator`'s favour and this README used to name the loser.
|
|
44
|
+
`FatigueLights.tsx:12` still imports the deprecated `StatusDot`; that call site migrates
|
|
45
|
+
under AW-127 (`DEPRECATIONS.md`). Compose `Indicator` in anything new here.
|
|
43
46
|
- **`Tooltip`** (ui/tooltip/) — the per-dimension hover detail.
|
|
44
47
|
- **`VelocityStrip`** (Workout/) — the hero reuses it verbatim; `VelocityHero` only adds
|
|
45
48
|
the loss-relative VL20/VL30 band overlay on the strip's own peak scale.
|
|
@@ -76,6 +79,34 @@ The reusable pure helpers (`ghostLineColor`, `auraForVerdict`, `mixHex`) live in
|
|
|
76
79
|
- **Tempo carried by the ghost-spark phase BAND** (`PHASE_AXIS_COLOR`) — no mini-tempo
|
|
77
80
|
digits, and no standalone hero-tempo component.
|
|
78
81
|
|
|
82
|
+
## Responsive geometry (TD-03.58 / TD-03.60)
|
|
83
|
+
|
|
84
|
+
`panel-layout.ts` is the panel's single geometry source — a pure module with no
|
|
85
|
+
`react-native` import, so the tiers are unit-tested without `onLayout` (which never fires
|
|
86
|
+
under jsdom, gotcha 6). `LiveFatiguePanel` measures its own width (`SIZE-D01`:
|
|
87
|
+
container-driven, not a `size` prop) and calls it; `containerWidth` overrides the
|
|
88
|
+
measurement for tests and for a consumer that already knows the width.
|
|
89
|
+
|
|
90
|
+
| tier | width | layout | padding · gap | card width |
|
|
91
|
+
| --- | --- | --- | --- | --- |
|
|
92
|
+
| `xs` | < 600 | stacked | 16 · 12 | full content width |
|
|
93
|
+
| `sm` | 600–999 | stacked | 20 · 14 | full content width |
|
|
94
|
+
| `md` | 1000–1199 | row | 24 · 18 | 318 |
|
|
95
|
+
| `lg` | 1200–1919 | row | 24 · 18 | 318 |
|
|
96
|
+
| `xl` | ≥ 1920 | row | 24 · 18 | `0.22 × width`, clamped to 318–460 |
|
|
97
|
+
|
|
98
|
+
The edges are titan's own `primitiveBreakpoints`, asserted by identity in
|
|
99
|
+
`panel-layout.test.ts` so nobody can quietly swap in a hand-picked set. Padding and gap
|
|
100
|
+
are deliberately frozen from `md` up: voltras-mcp's `panel-geometry.ts` derives its stage
|
|
101
|
+
chrome from them, and moving them would overflow the wall stage.
|
|
102
|
+
|
|
103
|
+
`panelBodySplit` is the one-height rule: side by side, the hero and the card both take the
|
|
104
|
+
whole `bodyHeight`; stacked, they share it (card 0.55, gap taken out first). Both numbers
|
|
105
|
+
come from one call, so `bodyHeight` moves both or neither.
|
|
106
|
+
|
|
107
|
+
Nothing here animates. A tier change is a re-layout, never a transition — the panel is
|
|
108
|
+
read from across a room mid-set, and the visual baselines need a deterministic render.
|
|
109
|
+
|
|
79
110
|
## Data plan / follow-ups
|
|
80
111
|
|
|
81
112
|
- **Store wiring:** `LiveFatigueCard` consumes `LiveFatigueModel` from voltras-mcp's
|
|
@@ -83,6 +114,12 @@ The reusable pure helpers (`ghostLineColor`, `auraForVerdict`, `mixHex`) live in
|
|
|
83
114
|
hand-rolled `live-page/FatigueCard.tsx` spike. The velocity hero's per-rep velocities
|
|
84
115
|
are NOT on the model (they come from the live-view velocity path) — passed as the
|
|
85
116
|
panel's separate `velocity` prop.
|
|
117
|
+
- **Open — voltras-mcp duplicates the panel geometry:** `panel-geometry.ts` hardcodes
|
|
118
|
+
`FATIGUE_CARD_WIDTH = 318`, `PANEL_PAD = 24` and `HERO_EYEBROW_ALLOWANCE = 26` so the
|
|
119
|
+
idle stage can prefigure the panel. Those are now derivable — `panelLayout` /
|
|
120
|
+
`panelBodySplit` / `HERO_EYEBROW_ALLOWANCE` are exported from this family's barrel. Until
|
|
121
|
+
the SPA imports them, its idle stage will draw a 318 card placeholder where the live panel
|
|
122
|
+
draws an expanded one on an `xl` display.
|
|
86
123
|
- **Deferred — VelocityStrip loss-relative bar fill:** the locked call also wants the
|
|
87
124
|
hero's BAR FILL recoloured gold→orange→red by velocity loss (not absolute zone).
|
|
88
125
|
VelocityStrip has no loss-relative colour mode; `VelocityHero` owns the band overlay
|
|
@@ -9,9 +9,9 @@ const PAGE_BG = greyRamp[975]
|
|
|
9
9
|
const t = getSemanticColors('dark')
|
|
10
10
|
|
|
11
11
|
const meta: Meta<typeof RomProgressionChart> = {
|
|
12
|
-
title: '
|
|
12
|
+
title: 'Custom/Fatigue/ROM Progression',
|
|
13
13
|
component: RomProgressionChart,
|
|
14
|
-
tags: ['autodocs'],
|
|
14
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
15
15
|
parameters: {
|
|
16
16
|
docs: {
|
|
17
17
|
description: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
2
|
/**
|
|
3
|
-
* `
|
|
3
|
+
* `Foundations/Color/Silver-Red Scheme` — the single documented source for the fatigue
|
|
4
4
|
* family's line/quality colour language. SILVER when the rep is right, SHADES OF RED when
|
|
5
5
|
* something's wrong — no greens, no ambers (those belong to the verdict tones + velocity-
|
|
6
6
|
* loss bands, not here). Every swatch reads its value straight from `fatigue-tokens`, so
|
|
@@ -29,6 +29,7 @@ const t = getSemanticColors('dark')
|
|
|
29
29
|
|
|
30
30
|
const meta: Meta = {
|
|
31
31
|
title: 'Foundations/Color/Silver-Red Scheme',
|
|
32
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
32
33
|
parameters: {
|
|
33
34
|
layout: 'fullscreen',
|
|
34
35
|
docs: {
|
|
@@ -10,9 +10,9 @@ const PANEL_BG = greyRamp[950]
|
|
|
10
10
|
const t = getSemanticColors('dark')
|
|
11
11
|
|
|
12
12
|
const meta: Meta<typeof VelocityHero> = {
|
|
13
|
-
title: '
|
|
13
|
+
title: 'Custom/Fatigue/Velocity Hero',
|
|
14
14
|
component: VelocityHero,
|
|
15
|
-
tags: ['autodocs'],
|
|
15
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
16
16
|
parameters: {
|
|
17
17
|
docs: {
|
|
18
18
|
description: {
|
|
@@ -20,7 +20,8 @@ const meta: Meta<typeof VelocityHero> = {
|
|
|
20
20
|
'The shipped `VelocityStrip` hero with LOSS-RELATIVE VL20/VL30 decision bands layered behind ' +
|
|
21
21
|
'the bars (on the hero’s own peak scale). The velocity-loss language lives here so the ' +
|
|
22
22
|
'fatigue card carries no separate VL chart. (Deferred: recolouring the bar FILL loss-relative ' +
|
|
23
|
-
'needs a new VelocityStrip prop — this component owns the band overlay only.)'
|
|
23
|
+
'needs a new VelocityStrip prop — this component owns the band overlay only.)' +
|
|
24
|
+
' Composes **VelocityStrip**.',
|
|
24
25
|
},
|
|
25
26
|
},
|
|
26
27
|
},
|
|
@@ -9,9 +9,9 @@ const PAGE_BG = greyRamp[975]
|
|
|
9
9
|
const t = getSemanticColors('dark')
|
|
10
10
|
|
|
11
11
|
const meta: Meta<typeof VerdictHero> = {
|
|
12
|
-
title: '
|
|
12
|
+
title: 'Custom/Fatigue/Verdict Hero',
|
|
13
13
|
component: VerdictHero,
|
|
14
|
-
tags: ['autodocs'],
|
|
14
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
15
15
|
parameters: {
|
|
16
16
|
docs: {
|
|
17
17
|
description: {
|
|
@@ -36,11 +36,6 @@ describe('buildMockPanelState', () => {
|
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
it('counts the header rep out of the same planned total', () => {
|
|
40
|
-
const { header } = buildMockPanelState(3)
|
|
41
|
-
expect(header.meta).toBe(`SET 3 · REP 4 / ${MOCK_PLANNED_REPS}`)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
39
|
it('derives the planned count from the rep data, not a literal', () => {
|
|
45
40
|
expect(MOCK_PLANNED_REPS).toBe(MOCK_MEAN_VELOCITIES.length)
|
|
46
41
|
})
|
|
@@ -177,8 +177,8 @@ export function buildMockModel(
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
|
-
* The WHOLE panel's input for a set truncated at `current` — model
|
|
181
|
-
*
|
|
180
|
+
* The WHOLE panel's input for a set truncated at `current` — model and velocity from one
|
|
181
|
+
* rep count and one truncation point.
|
|
182
182
|
*
|
|
183
183
|
* The panel feeds two components that each draw their own upcoming-rep remainder, so
|
|
184
184
|
* assembling their props separately is how the two halves drift apart. Build both here
|
|
@@ -190,7 +190,6 @@ export function buildMockPanelState(
|
|
|
190
190
|
): {
|
|
191
191
|
model: LiveFatigueModel
|
|
192
192
|
velocity: { velocities: number[]; targetReps: number; liveRepIndex: number }
|
|
193
|
-
header: typeof MOCK_HEADER
|
|
194
193
|
} {
|
|
195
194
|
const model = buildMockModel(current, overrides)
|
|
196
195
|
return {
|
|
@@ -201,7 +200,6 @@ export function buildMockPanelState(
|
|
|
201
200
|
targetReps: MOCK_PLANNED_REPS,
|
|
202
201
|
liveRepIndex: current,
|
|
203
202
|
},
|
|
204
|
-
header: { ...MOCK_HEADER, meta: `SET 3 · REP ${current + 1} / ${MOCK_PLANNED_REPS}` },
|
|
205
203
|
}
|
|
206
204
|
}
|
|
207
205
|
|
|
@@ -260,9 +258,3 @@ export const FATIGUE_STATES: Array<{ name: string; current: number; model: LiveF
|
|
|
260
258
|
|
|
261
259
|
/** A warming-up (cold-start, < 2 reps) model — null verdict / RPE. */
|
|
262
260
|
export const WARMING_UP_MODEL: LiveFatigueModel = buildMockModel(0, { rpe: null, verdict: null })
|
|
263
|
-
|
|
264
|
-
export const MOCK_HEADER = {
|
|
265
|
-
title: 'Cable Chest Press',
|
|
266
|
-
subtitle: `Push A · Hypertrophy · 62 lb × ${MOCK_PLANNED_REPS} · tempo 2.6·0.4·0.95·0.28`,
|
|
267
|
-
meta: `SET 3 · REP ${MOCK_PLANNED_REPS} / ${MOCK_PLANNED_REPS}`,
|
|
268
|
-
}
|
|
@@ -12,6 +12,18 @@ const t = getSemanticColors('dark')
|
|
|
12
12
|
|
|
13
13
|
export const FONT_HEAD = '"Space Grotesk", sans-serif'
|
|
14
14
|
export const FONT_UI = '"Nunito Sans", sans-serif'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FOUNDATIONS GAP (TD-03.59 debt 3) — deliberately still the generic CSS fallback.
|
|
18
|
+
*
|
|
19
|
+
* titan has TWO mono declarations and they disagree, so adopting either here would be
|
|
20
|
+
* choosing a typeface rather than resolving a token:
|
|
21
|
+
* `primitiveTypography.fontFamilies.mono` SF Mono · Monaco · Inconsolata · Fira Mono · …
|
|
22
|
+
* `--font-family-mono` (global.css, both themes) SFMono-Regular · Consolas · Liberation Mono · Menlo
|
|
23
|
+
* They also pick different first faces on macOS, so the two render at different metrics.
|
|
24
|
+
* Reconciling them is a foundations decision; until it lands this stays `monospace` so the
|
|
25
|
+
* wall render does not move under a half-made choice.
|
|
26
|
+
*/
|
|
15
27
|
export const FONT_MONO = 'monospace'
|
|
16
28
|
|
|
17
29
|
/** Per-dimension tone → semantic status colour (the one language the dots + hero share). */
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
// Live-fatigue component family — the aligned "Live panel v2" hardened into titan
|
|
2
2
|
// components. See ./README.md for the composition tree + reuse audit.
|
|
3
3
|
export { LiveFatiguePanel } from './LiveFatiguePanel'
|
|
4
|
-
export type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export type { LiveFatiguePanelProps, LiveFatiguePanelVelocity } from './LiveFatiguePanel'
|
|
5
|
+
// The panel's responsive geometry, exported so a consumer that prefigures the panel (the
|
|
6
|
+
// wall SPA's idle stage) reads the same numbers instead of copying them.
|
|
7
|
+
export {
|
|
8
|
+
panelLayout,
|
|
9
|
+
panelBodySplit,
|
|
10
|
+
panelTier,
|
|
11
|
+
PANEL_BREAKPOINTS,
|
|
12
|
+
CARD_WIDTH_BASE,
|
|
13
|
+
CARD_WIDTH_MAX,
|
|
14
|
+
CARD_WIDTH_XL_RATIO,
|
|
15
|
+
CARD_HEIGHT_SHARE_STACKED,
|
|
16
|
+
HERO_EYEBROW_ALLOWANCE,
|
|
17
|
+
type PanelLayout,
|
|
18
|
+
type PanelBodySplit,
|
|
19
|
+
type PanelTier,
|
|
20
|
+
} from './panel-layout'
|
|
9
21
|
export { LiveFatigueCard, type LiveFatigueCardProps } from './LiveFatigueCard'
|
|
10
22
|
export { VerdictHero, type VerdictHeroProps } from './VerdictHero'
|
|
11
23
|
export { FatigueLights, type FatigueLightsProps } from './FatigueLights'
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { primitiveBreakpoints } from '../../../theme/tokens/primitives'
|
|
3
|
+
import {
|
|
4
|
+
PANEL_BREAKPOINTS,
|
|
5
|
+
CARD_WIDTH_BASE,
|
|
6
|
+
CARD_WIDTH_MAX,
|
|
7
|
+
CARD_WIDTH_XL_RATIO,
|
|
8
|
+
CARD_HEIGHT_SHARE_STACKED,
|
|
9
|
+
CARD_MIN_HEIGHT_STACKED,
|
|
10
|
+
CARD_MIN_CHART_HEIGHT,
|
|
11
|
+
CARD_MAX_CHART_HEIGHT,
|
|
12
|
+
CARD_NATURAL_CHART_HEIGHT,
|
|
13
|
+
HERO_EYEBROW_ALLOWANCE,
|
|
14
|
+
HERO_MIN_PLOT_HEIGHT,
|
|
15
|
+
panelTier,
|
|
16
|
+
panelLayout,
|
|
17
|
+
panelBodySplit,
|
|
18
|
+
cardChartHeight,
|
|
19
|
+
} from './panel-layout'
|
|
20
|
+
|
|
21
|
+
describe('panel breakpoints', () => {
|
|
22
|
+
// The edges are titan's, not this component's. If someone re-points the panel at a
|
|
23
|
+
// hand-picked set, this is where it shows up.
|
|
24
|
+
it('are titan primitiveBreakpoints, not values invented here', () => {
|
|
25
|
+
expect(PANEL_BREAKPOINTS).toBe(primitiveBreakpoints)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it.each([
|
|
29
|
+
[320, 'xs'],
|
|
30
|
+
[599, 'xs'],
|
|
31
|
+
[600, 'sm'],
|
|
32
|
+
[999, 'sm'],
|
|
33
|
+
[1000, 'md'],
|
|
34
|
+
[1199, 'md'],
|
|
35
|
+
[1200, 'lg'],
|
|
36
|
+
[1919, 'lg'],
|
|
37
|
+
[1920, 'xl'],
|
|
38
|
+
[2560, 'xl'],
|
|
39
|
+
])('puts %ipx in the %s tier', (width, tier) => {
|
|
40
|
+
expect(panelTier(width)).toBe(tier)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('treats an unmeasured container as md so the first paint is the row layout', () => {
|
|
44
|
+
expect(panelTier(0)).toBe('md')
|
|
45
|
+
expect(panelLayout(0).stacked).toBe(false)
|
|
46
|
+
expect(panelLayout(0).cardWidth).toBe(CARD_WIDTH_BASE)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('panelLayout', () => {
|
|
51
|
+
it('stacks below the md edge and rows at or above it', () => {
|
|
52
|
+
expect(panelLayout(PANEL_BREAKPOINTS.md - 1).stacked).toBe(true)
|
|
53
|
+
expect(panelLayout(PANEL_BREAKPOINTS.md).stacked).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('holds the shipped padding, gap and card width at md and lg', () => {
|
|
57
|
+
for (const width of [1000, 1200, 1600, 1919]) {
|
|
58
|
+
expect(panelLayout(width)).toMatchObject({
|
|
59
|
+
stacked: false,
|
|
60
|
+
padding: 24,
|
|
61
|
+
gap: 18,
|
|
62
|
+
cardWidth: CARD_WIDTH_BASE,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('expands the card at wall width without moving the padding or the gap', () => {
|
|
68
|
+
const wall = panelLayout(1920)
|
|
69
|
+
expect(wall.cardWidth).toBe(Math.round(1920 * CARD_WIDTH_XL_RATIO))
|
|
70
|
+
expect(wall.cardWidth).toBeGreaterThan(CARD_WIDTH_BASE)
|
|
71
|
+
// The SPA's stage chrome is derived from these two; moving them would overflow it.
|
|
72
|
+
expect(wall.padding).toBe(24)
|
|
73
|
+
expect(wall.gap).toBe(18)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('caps the expansion so the card charts stay near the width they were drawn at', () => {
|
|
77
|
+
expect(panelLayout(4000).cardWidth).toBe(CARD_WIDTH_MAX)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('gives a stacked card the full content width', () => {
|
|
81
|
+
const narrow = panelLayout(600)
|
|
82
|
+
expect(narrow.stacked).toBe(true)
|
|
83
|
+
expect(narrow.cardWidth).toBe(600 - narrow.padding * 2)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('panelBodySplit — one height source', () => {
|
|
88
|
+
// TD-03.60: the hero and the card must not be able to disagree about the body height.
|
|
89
|
+
it('moves BOTH the hero and the card when bodyHeight changes, in every tier', () => {
|
|
90
|
+
for (const width of [480, 900, 1000, 1440, 1920]) {
|
|
91
|
+
const layout = panelLayout(width)
|
|
92
|
+
const small = panelBodySplit(800, layout)
|
|
93
|
+
const large = panelBodySplit(1200, layout)
|
|
94
|
+
expect(large.heroHeight).toBeGreaterThan(small.heroHeight)
|
|
95
|
+
expect(large.cardHeight).toBeGreaterThan(small.cardHeight)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('gives the row layout the full height to both, less the hero eyebrow', () => {
|
|
100
|
+
const split = panelBodySplit(508, panelLayout(1440))
|
|
101
|
+
expect(split.cardHeight).toBe(508)
|
|
102
|
+
expect(split.heroHeight).toBe(508 - HERO_EYEBROW_ALLOWANCE)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('spends the whole stacked height on the two blocks and the gap, nothing lost', () => {
|
|
106
|
+
const layout = panelLayout(700)
|
|
107
|
+
const split = panelBodySplit(900, layout)
|
|
108
|
+
expect(split.heroHeight + HERO_EYEBROW_ALLOWANCE + split.cardHeight + layout.gap).toBe(900)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('gives the stacked card the larger share once there is height to share', () => {
|
|
112
|
+
const layout = panelLayout(700)
|
|
113
|
+
const split = panelBodySplit(900, layout)
|
|
114
|
+
expect(split.cardHeight).toBe(Math.round((900 - layout.gap) * CARD_HEIGHT_SHARE_STACKED))
|
|
115
|
+
expect(split.cardHeight).toBeGreaterThan(split.heroHeight)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// A stacked card shorter than its content spilled the ghost spark outside the rounded
|
|
119
|
+
// edge — measured at 301px against a 384px content height before the floor existed.
|
|
120
|
+
it('never draws a stacked card below its content floor', () => {
|
|
121
|
+
for (const bodyHeight of [200, 400, 560, 600]) {
|
|
122
|
+
const split = panelBodySplit(bodyHeight, panelLayout(480))
|
|
123
|
+
expect(split.cardHeight).toBeGreaterThanOrEqual(CARD_MIN_HEIGHT_STACKED)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('keeps the stacked hero plot readable rather than flattening it to nothing', () => {
|
|
128
|
+
const split = panelBodySplit(300, panelLayout(480))
|
|
129
|
+
expect(split.heroHeight).toBe(HERO_MIN_PLOT_HEIGHT)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('never returns a negative height for a body too short to hold the chrome', () => {
|
|
133
|
+
const split = panelBodySplit(10, panelLayout(700))
|
|
134
|
+
expect(split.heroHeight).toBeGreaterThanOrEqual(0)
|
|
135
|
+
expect(split.cardHeight).toBeGreaterThanOrEqual(0)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('cardChartHeight', () => {
|
|
140
|
+
it('gives an unpinned card its natural plot height', () => {
|
|
141
|
+
expect(cardChartHeight(undefined)).toBe(CARD_NATURAL_CHART_HEIGHT)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('clamps a pinned card between the floor and the ceiling', () => {
|
|
145
|
+
expect(cardChartHeight(100)).toBe(CARD_MIN_CHART_HEIGHT)
|
|
146
|
+
expect(cardChartHeight(5000)).toBe(CARD_MAX_CHART_HEIGHT)
|
|
147
|
+
expect(cardChartHeight(508)).toBe(Math.round(508 * 0.4))
|
|
148
|
+
})
|
|
149
|
+
})
|