@titan-design/react-ui 0.2.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/bodymap-BhG55xHM.d.mts +318 -0
  2. package/dist/bodymap-BhG55xHM.d.ts +318 -0
  3. package/dist/bodymap.d.mts +3 -0
  4. package/dist/bodymap.d.ts +3 -0
  5. package/dist/bodymap.js +3014 -0
  6. package/dist/bodymap.js.map +1 -0
  7. package/dist/bodymap.mjs +1088 -0
  8. package/dist/bodymap.mjs.map +1 -0
  9. package/dist/chunk-2SISKTWM.mjs +1027 -0
  10. package/dist/chunk-2SISKTWM.mjs.map +1 -0
  11. package/dist/{chunk-5SSKGS6E.mjs → chunk-7XPB4NAO.mjs} +124 -4
  12. package/dist/chunk-7XPB4NAO.mjs.map +1 -0
  13. package/dist/{chunk-3VLOBS6O.mjs → chunk-SNVKL5WZ.mjs} +42 -5
  14. package/dist/chunk-SNVKL5WZ.mjs.map +1 -0
  15. package/dist/index.d.mts +416 -250
  16. package/dist/index.d.ts +416 -250
  17. package/dist/index.js +1567 -1123
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +1491 -2201
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/theme/index.d.mts +288 -2
  22. package/dist/theme/index.d.ts +288 -2
  23. package/dist/theme/index.js +159 -2
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/index.mjs +2 -2
  26. package/dist/theme/tokens-css.d.mts +5 -3
  27. package/dist/theme/tokens-css.d.ts +5 -3
  28. package/dist/theme/tokens-css.js +318 -9
  29. package/dist/theme/tokens-css.js.map +1 -1
  30. package/dist/theme/tokens-css.mjs +1 -1
  31. package/dist/theme/tokens-css.mjs.map +1 -1
  32. package/package.json +10 -1
  33. package/src/bodymap.ts +37 -0
  34. package/src/components/custom/Table/Table.test.tsx +27 -0
  35. package/src/components/custom/Table/Table.tsx +33 -17
  36. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +118 -0
  37. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +171 -0
  38. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +442 -0
  39. package/src/components/custom/Workout/BodyMap.tsx +7 -9
  40. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +25 -29
  41. package/src/components/custom/Workout/CapacityBandChart.tsx +2 -3
  42. package/src/components/custom/Workout/ExerciseCard.test.tsx +24 -0
  43. package/src/components/custom/Workout/ExerciseCard.tsx +24 -26
  44. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +225 -0
  45. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +128 -0
  46. package/src/components/custom/Workout/ExerciseDetailPage.tsx +624 -0
  47. package/src/components/custom/Workout/InputBar.tsx +10 -12
  48. package/src/components/custom/Workout/MesoCard.tsx +2 -4
  49. package/src/components/custom/Workout/MesoProgressBar.test.tsx +12 -0
  50. package/src/components/custom/Workout/MesoProgressBar.tsx +5 -1
  51. package/src/components/custom/Workout/MesoStatusCard.tsx +17 -15
  52. package/src/components/custom/Workout/MuscleGroupChip.tsx +2 -4
  53. package/src/components/custom/Workout/PlaceholderStrip.tsx +2 -4
  54. package/src/components/custom/Workout/PrHistoryModal.tsx +12 -14
  55. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +257 -0
  56. package/src/components/custom/Workout/ProgramPlanningPage.test.tsx +142 -0
  57. package/src/components/custom/Workout/ProgramPlanningPage.tsx +418 -0
  58. package/src/components/custom/Workout/ReadinessCheck.tsx +13 -12
  59. package/src/components/custom/Workout/RestTimer.test.tsx +10 -0
  60. package/src/components/custom/Workout/RestTimer.tsx +12 -9
  61. package/src/components/custom/Workout/SetRow.tsx +16 -19
  62. package/src/components/custom/Workout/StatusDot.tsx +1 -0
  63. package/src/components/custom/Workout/StrengthTrendChart.tsx +15 -17
  64. package/src/components/custom/Workout/TempoBar.stories.tsx +95 -0
  65. package/src/components/custom/Workout/TempoBar.test.tsx +103 -0
  66. package/src/components/custom/Workout/TempoBar.tsx +215 -0
  67. package/src/components/custom/Workout/TempoDisplay.tsx +3 -1
  68. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +212 -0
  69. package/src/components/custom/Workout/TrainingStatusPage.test.tsx +131 -0
  70. package/src/components/custom/Workout/TrainingStatusPage.tsx +312 -0
  71. package/src/components/custom/Workout/VelocityStrip.test.tsx +10 -0
  72. package/src/components/custom/Workout/VelocityStrip.tsx +19 -16
  73. package/src/components/custom/Workout/WeekRow.tsx +2 -1
  74. package/src/components/custom/Workout/WorkoutCard.tsx +3 -6
  75. package/src/components/custom/Workout/index.ts +80 -32
  76. package/src/test/nativewind-stub.ts +13 -0
  77. package/src/theme/ThemeProvider.test.tsx +24 -0
  78. package/src/theme/ThemeProvider.tsx +50 -0
  79. package/src/theme/config.completeness.test.ts +77 -0
  80. package/src/theme/config.ts +151 -0
  81. package/src/theme/index.ts +2 -0
  82. package/src/theme/manifest/design-freeze.test.ts +155 -0
  83. package/src/theme/manifest/extract-css-properties.fixture.html +29 -0
  84. package/src/theme/manifest/extract-css-properties.test.ts +112 -0
  85. package/src/theme/resolve-color.ts +19 -0
  86. package/src/theme/tokens-css.ts +5 -3
  87. package/src/theme/workout-tokens.ts +12 -14
  88. package/src/utils/index.ts +10 -0
  89. package/src/utils/workout-format.test.ts +81 -0
  90. package/src/utils/workout-format.ts +83 -0
  91. package/dist/chunk-3VLOBS6O.mjs.map +0 -1
  92. package/dist/chunk-5SSKGS6E.mjs.map +0 -1
@@ -11,7 +11,6 @@ import {
11
11
  type ViewStyle,
12
12
  } from 'react-native'
13
13
  import { Badge, type BadgeColor } from '../../ui/badge'
14
- import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
15
14
  import { Sparkline } from './Sparkline'
16
15
  import {
17
16
  MuscleGroup,
@@ -20,14 +19,7 @@ import {
20
19
  type VolumeStatus,
21
20
  } from './muscleTaxonomy'
22
21
 
23
- const SURFACE_ELEVATED = WORKOUT_TOKENS.surface.elevated
24
- const SURFACE_RAISED = WORKOUT_TOKENS.surface.raised
25
- const BORDER_STRONG = WORKOUT_TOKENS.border.strong
26
- const BORDER_DEFAULT = WORKOUT_TOKENS.border.default
27
22
  const BRAND_PRIMARY = '#FF7900'
28
- const TEXT_PRIMARY = '#F3F4F6'
29
- const TEXT_SECONDARY = '#9CA3AF'
30
- const TEXT_TERTIARY = '#6B7280'
31
23
 
32
24
  /** Volume track gradient: status-info (blue) -> result-improve (teal) -> status-error (red). */
33
25
  const VOLUME_GRADIENT = 'linear-gradient(90deg, #2196F3 0%, #14B8A6 50%, #D14343 100%)'
@@ -206,8 +198,8 @@ export function BodyMapDetailPanel({
206
198
  accessibilityRole={'dialog' as ViewProps['accessibilityRole']}
207
199
  accessibilityLabel={dialogLabel}
208
200
  aria-label={dialogLabel}
201
+ className="bg-surface-elevated"
209
202
  style={{
210
- backgroundColor: SURFACE_ELEVATED,
211
203
  borderTopLeftRadius: 16,
212
204
  borderTopRightRadius: 16,
213
205
  maxHeight: '88%',
@@ -225,7 +217,8 @@ export function BodyMapDetailPanel({
225
217
  testID="body-map-detail-panel-handle"
226
218
  >
227
219
  <View
228
- style={{ width: 40, height: 4, borderRadius: 2, backgroundColor: BORDER_STRONG }}
220
+ className="bg-border-strong"
221
+ style={{ width: 40, height: 4, borderRadius: 2 }}
229
222
  accessibilityElementsHidden
230
223
  />
231
224
  </Pressable>
@@ -243,11 +236,11 @@ export function BodyMapDetailPanel({
243
236
  <View className="flex-row items-center" style={{ gap: 8, flexShrink: 1 }}>
244
237
  <Text
245
238
  accessibilityRole="header"
239
+ className="text-text-primary"
246
240
  style={{
247
241
  fontSize: 16,
248
242
  fontFamily: '"Space Grotesk", sans-serif',
249
243
  fontWeight: '700',
250
- color: TEXT_PRIMARY,
251
244
  }}
252
245
  testID="body-map-detail-panel-title"
253
246
  >
@@ -270,13 +263,14 @@ export function BodyMapDetailPanel({
270
263
  style={{ padding: 4, margin: -4 }}
271
264
  testID="body-map-detail-panel-close"
272
265
  >
273
- <Text style={{ fontSize: 22, lineHeight: 22, color: TEXT_SECONDARY }}>{'×'}</Text>
266
+ <Text className="text-text-secondary" style={{ fontSize: 22, lineHeight: 22 }}>{'×'}</Text>
274
267
  </Pressable>
275
268
  </View>
276
269
 
277
270
  {lastTrained != null && (
278
271
  <Text
279
- style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color: TEXT_SECONDARY }}
272
+ className="text-text-secondary"
273
+ style={{ fontSize: 12, fontFamily: 'Inter, sans-serif' }}
280
274
  testID="body-map-detail-panel-last-trained"
281
275
  >
282
276
  {`Last trained ${lastTrained}`}
@@ -289,10 +283,10 @@ export function BodyMapDetailPanel({
289
283
  style={{ marginBottom: 6 }}
290
284
  accessibilityElementsHidden
291
285
  >
292
- <Text style={{ fontSize: 10, fontFamily: 'Inter, sans-serif', fontWeight: '600', color: TEXT_TERTIARY }}>
286
+ <Text className="text-text-tertiary" style={{ fontSize: 10, fontFamily: 'Inter, sans-serif', fontWeight: '600' }}>
293
287
  {`MEV ${landmarks.mev}`}
294
288
  </Text>
295
- <Text style={{ fontSize: 10, fontFamily: 'Inter, sans-serif', fontWeight: '600', color: TEXT_TERTIARY }}>
289
+ <Text className="text-text-tertiary" style={{ fontSize: 10, fontFamily: 'Inter, sans-serif', fontWeight: '600' }}>
296
290
  {`MRV ${landmarks.mrv}`}
297
291
  </Text>
298
292
  </View>
@@ -311,6 +305,7 @@ export function BodyMapDetailPanel({
311
305
  accessibilityElementsHidden
312
306
  />
313
307
  <View
308
+ className="border-text-primary bg-surface-elevated"
314
309
  style={{
315
310
  position: 'absolute',
316
311
  left: `${markerLeft}%`,
@@ -319,8 +314,6 @@ export function BodyMapDetailPanel({
319
314
  height: 14,
320
315
  borderRadius: 7,
321
316
  borderWidth: 2,
322
- borderColor: TEXT_PRIMARY,
323
- backgroundColor: SURFACE_ELEVATED,
324
317
  boxShadow: '0 0 4px rgba(0,0,0,0.5)',
325
318
  }}
326
319
  accessibilityElementsHidden
@@ -331,18 +324,19 @@ export function BodyMapDetailPanel({
331
324
 
332
325
  <View className="flex-row items-baseline" style={{ marginTop: 14, gap: 4 }}>
333
326
  <Text
327
+ className="text-text-primary"
334
328
  style={{
335
329
  fontSize: 24,
336
330
  fontFamily: '"Space Grotesk", sans-serif',
337
331
  fontWeight: '700',
338
- color: TEXT_PRIMARY,
339
332
  }}
340
333
  testID="body-map-detail-panel-set-count"
341
334
  >
342
335
  {weeklySets}
343
336
  </Text>
344
337
  <Text
345
- style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color: TEXT_SECONDARY }}
338
+ className="text-text-secondary"
339
+ style={{ fontSize: 12, fontFamily: 'Inter, sans-serif' }}
346
340
  testID="body-map-detail-panel-mrv-suffix"
347
341
  >
348
342
  {`/ ${landmarks.mrv} MRV`}
@@ -352,11 +346,11 @@ export function BodyMapDetailPanel({
352
346
  {weeklyHistory != null && weeklyHistory.length > 0 && (
353
347
  <View style={{ marginTop: 14 }} testID="body-map-detail-panel-sparkline">
354
348
  <Text
349
+ className="text-text-tertiary"
355
350
  style={{
356
351
  fontSize: 10,
357
352
  fontFamily: 'Inter, sans-serif',
358
353
  fontWeight: '600',
359
- color: TEXT_TERTIARY,
360
354
  marginBottom: 6,
361
355
  }}
362
356
  accessibilityElementsHidden
@@ -370,11 +364,11 @@ export function BodyMapDetailPanel({
370
364
  {hasContributing && (
371
365
  <View style={{ marginTop: 16 }} testID="body-map-detail-panel-contributing">
372
366
  <Text
367
+ className="text-text-tertiary"
373
368
  style={{
374
369
  fontSize: 10,
375
370
  fontFamily: 'Inter, sans-serif',
376
371
  fontWeight: '600',
377
- color: TEXT_TERTIARY,
378
372
  marginBottom: 8,
379
373
  }}
380
374
  >
@@ -383,27 +377,27 @@ export function BodyMapDetailPanel({
383
377
  {contributingExercises!.map((exercise, index) => (
384
378
  <View
385
379
  key={`${exercise.name}-${index}`}
386
- className="flex-row items-center justify-between"
380
+ className="flex-row items-center justify-between bg-surface-raised border-border"
387
381
  style={{
388
382
  gap: 10,
389
383
  paddingVertical: 8,
390
384
  paddingHorizontal: 12,
391
385
  marginBottom: 6,
392
386
  borderRadius: 8,
393
- backgroundColor: SURFACE_RAISED,
394
387
  borderWidth: 1,
395
- borderColor: BORDER_DEFAULT,
396
388
  }}
397
389
  testID={`body-map-detail-panel-contributing-${index}`}
398
390
  >
399
391
  <Text
400
- style={{ flex: 1, fontSize: 13, fontFamily: 'Inter, sans-serif', fontWeight: '600', color: TEXT_PRIMARY }}
392
+ className="text-text-primary"
393
+ style={{ flex: 1, fontSize: 13, fontFamily: 'Inter, sans-serif', fontWeight: '600' }}
401
394
  testID={`body-map-detail-panel-contributing-${index}-name`}
402
395
  >
403
396
  {exercise.name}
404
397
  </Text>
405
398
  <Text
406
- style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color: TEXT_SECONDARY }}
399
+ className="text-text-secondary"
400
+ style={{ fontSize: 12, fontFamily: 'Inter, sans-serif' }}
407
401
  testID={`body-map-detail-panel-contributing-${index}-detail`}
408
402
  >
409
403
  {`${exercise.sets} sets · ${Math.round(exercise.contributionWeight * 100)}%`}
@@ -416,11 +410,11 @@ export function BodyMapDetailPanel({
416
410
  {hasUpcoming && (
417
411
  <View style={{ marginTop: 16 }} testID="body-map-detail-panel-upcoming">
418
412
  <Text
413
+ className="text-text-tertiary"
419
414
  style={{
420
415
  fontSize: 10,
421
416
  fontFamily: 'Inter, sans-serif',
422
417
  fontWeight: '600',
423
- color: TEXT_TERTIARY,
424
418
  marginBottom: 8,
425
419
  }}
426
420
  >
@@ -434,13 +428,15 @@ export function BodyMapDetailPanel({
434
428
  testID={`body-map-detail-panel-upcoming-${index}`}
435
429
  >
436
430
  <Text
437
- style={{ flex: 1, fontSize: 13, fontFamily: 'Inter, sans-serif', fontWeight: '600', color: TEXT_PRIMARY }}
431
+ className="text-text-primary"
432
+ style={{ flex: 1, fontSize: 13, fontFamily: 'Inter, sans-serif', fontWeight: '600' }}
438
433
  testID={`body-map-detail-panel-upcoming-${index}-name`}
439
434
  >
440
435
  {exercise.name}
441
436
  </Text>
442
437
  <Text
443
- style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color: TEXT_TERTIARY }}
438
+ className="text-text-tertiary"
439
+ style={{ fontSize: 12, fontFamily: 'Inter, sans-serif' }}
444
440
  testID={`body-map-detail-panel-upcoming-${index}-detail`}
445
441
  >
446
442
  {`${exercise.workoutName} · ${exercise.sets} sets`}
@@ -8,7 +8,6 @@ const STATUS_INFO = '#2196F3'
8
8
  /** Dot outline: near-white ring so load dots read on the band fill and any
9
9
  * surface (matches the MesoStatusCard gauge-marker convention). */
10
10
  const DOT_BORDER = '#F3F4F6'
11
- const TEXT_TERTIARY = '#6B7280'
12
11
  const BAND_FILL = 'rgba(20,184,166,0.1)'
13
12
  const BAND_EDGE = 'rgba(20,184,166,0.45)'
14
13
  const PROJECTION_FILL = 'rgba(20,184,166,0.05)'
@@ -398,6 +397,7 @@ export function CapacityBandChart({
398
397
 
399
398
  {/* Y axis conceptual label (no numbers). */}
400
399
  <Text
400
+ className="text-text-tertiary"
401
401
  style={{
402
402
  position: 'absolute',
403
403
  left: PADDING_LEFT / 2 - 14,
@@ -406,7 +406,6 @@ export function CapacityBandChart({
406
406
  textAlign: 'center',
407
407
  fontSize: 9,
408
408
  fontFamily: 'Inter, sans-serif',
409
- color: TEXT_TERTIARY,
410
409
  transform: [{ rotate: '-90deg' }],
411
410
  }}
412
411
  accessibilityElementsHidden
@@ -422,6 +421,7 @@ export function CapacityBandChart({
422
421
  return (
423
422
  <Text
424
423
  key={`x-${point.date}`}
424
+ className="text-text-tertiary"
425
425
  style={{
426
426
  position: 'absolute',
427
427
  left: toX(point.date) - 14,
@@ -430,7 +430,6 @@ export function CapacityBandChart({
430
430
  textAlign: 'center',
431
431
  fontSize: 10,
432
432
  fontFamily: 'Inter, sans-serif',
433
- color: TEXT_TERTIARY,
434
433
  }}
435
434
  accessibilityElementsHidden
436
435
  importantForAccessibility="no-hide-descendants"
@@ -100,6 +100,30 @@ describe('ExerciseCard', () => {
100
100
  expect(headers).toHaveTextContent('RPE')
101
101
  })
102
102
 
103
+ it('shows the KG weight header when the unit is kg', () => {
104
+ render(
105
+ <ExerciseCard
106
+ {...expandedProps}
107
+ summary={{ sets: 3, reps: 8, weight: 84, unit: 'kg' }}
108
+ />,
109
+ )
110
+ const headers = screen.getByTestId('exercise-card-column-headers')
111
+ expect(headers).toHaveTextContent('KG')
112
+ expect(headers).not.toHaveTextContent('LBS')
113
+ })
114
+
115
+ it('derives the weight header from the sets unit when summary is absent', () => {
116
+ render(
117
+ <ExerciseCard
118
+ name="Squat"
119
+ state="expanded"
120
+ onToggle={vi.fn()}
121
+ sets={[{ mode: 'completed', setNumber: 1, reps: 8, weight: 60, unit: 'kg' }]}
122
+ />,
123
+ )
124
+ expect(screen.getByTestId('exercise-card-column-headers')).toHaveTextContent('KG')
125
+ })
126
+
103
127
  it('renders SetRow components', () => {
104
128
  render(<ExerciseCard {...expandedProps} />)
105
129
  const setRows = screen.getAllByTestId('set-row')
@@ -1,13 +1,14 @@
1
1
  // Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
2
2
  import { useState } from 'react'
3
3
  import { View, Text, Pressable } from 'react-native'
4
- import { WORKOUT_TOKENS } from '../../../theme/workout-tokens'
5
4
  import { VelocityStrip } from './VelocityStrip'
6
5
  import { PlaceholderStrip } from './PlaceholderStrip'
7
6
  import { WeightBadge } from './WeightBadge'
8
7
  import { PrBadge } from './PrBadge'
9
8
  import { TempoDisplay } from './TempoDisplay'
10
9
  import { SetRow, type SetRowProps } from './SetRow'
10
+ import { roundWeight } from '../../../utils/workout-format'
11
+ import { resolveColor } from '../../../theme/resolve-color'
11
12
 
12
13
  export type ExerciseCardState = 'collapsed' | 'expanded' | 'upcoming'
13
14
 
@@ -34,15 +35,11 @@ export interface ExerciseCardProps {
34
35
  supersetColor?: string
35
36
  }
36
37
 
37
- const COLORS = {
38
- textPrimary: '#F3F4F6',
39
- textSecondary: '#9CA3AF',
40
- textTertiary: '#6B7280',
41
- brandPrimary: '#FF7900',
38
+ /** Column headers; the weight column reflects the card's unit (LBS / KG). */
39
+ function buildColumnHeaders(unit: 'lbs' | 'kg'): string[] {
40
+ return ['SET', 'PREV', 'REPS', unit.toUpperCase(), 'RPE']
42
41
  }
43
42
 
44
- const COLUMN_HEADERS = ['SET', 'PREV', 'REPS', 'LBS', 'RPE'] as const
45
-
46
43
  function getSupersetBorderRadius(
47
44
  position: ExerciseCardProps['supersetPosition'],
48
45
  ): Record<string, number> {
@@ -89,7 +86,7 @@ function getSupersetMargin(
89
86
 
90
87
  function formatSummary(summary: ExerciseCardProps['summary']): string {
91
88
  if (!summary) return ''
92
- return `${summary.sets}\u00D7${summary.reps} @ ${summary.weight} ${summary.unit}`
89
+ return `${summary.sets}\u00D7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`
93
90
  }
94
91
 
95
92
  function formatAccessibilityLabel(
@@ -128,13 +125,11 @@ function CollapsedCard({
128
125
  testID="exercise-card"
129
126
  >
130
127
  <View
128
+ className={pressed ? 'bg-surface-raised' : undefined}
131
129
  style={{
132
130
  padding: 12,
133
131
  paddingHorizontal: 14,
134
132
  cursor: 'pointer',
135
- backgroundColor: pressed
136
- ? WORKOUT_TOKENS.surface.raised
137
- : 'transparent',
138
133
  ...borderRadius,
139
134
  ...supersetMargin,
140
135
  }}
@@ -144,11 +139,11 @@ function CollapsedCard({
144
139
  testID="exercise-card-header"
145
140
  >
146
141
  <Text
142
+ className="text-text-primary"
147
143
  style={{
148
144
  fontSize: 14,
149
145
  fontFamily: '"Space Grotesk", sans-serif',
150
146
  fontWeight: '700',
151
- color: COLORS.textPrimary,
152
147
  }}
153
148
  testID="exercise-card-name"
154
149
  >
@@ -157,10 +152,10 @@ function CollapsedCard({
157
152
  <View className="flex-1" />
158
153
  {summary && (
159
154
  <Text
155
+ className="text-text-secondary"
160
156
  style={{
161
157
  fontSize: 12,
162
158
  fontFamily: 'Inter, sans-serif',
163
- color: COLORS.textSecondary,
164
159
  marginRight: 8,
165
160
  }}
166
161
  testID="exercise-card-summary"
@@ -170,7 +165,7 @@ function CollapsedCard({
170
165
  )}
171
166
  {e1rm && (
172
167
  <WeightBadge
173
- value={e1rm.value}
168
+ value={roundWeight(e1rm.value)}
174
169
  unit={e1rm.unit}
175
170
  size="sm"
176
171
  showIcon={false}
@@ -223,13 +218,16 @@ function ExpandedCard({
223
218
  }: ExerciseCardProps) {
224
219
  const borderRadius = getSupersetBorderRadius(supersetPosition)
225
220
  const supersetMargin = getSupersetMargin(supersetPosition)
221
+ // Summary is the card-level authority for the weight column; fall back to the
222
+ // first set's unit, then lbs. (Mixed per-set units keep the card-level label.)
223
+ const unit = summary?.unit ?? sets?.[0]?.unit ?? 'lbs'
224
+ const columnHeaders = buildColumnHeaders(unit)
226
225
 
227
226
  return (
228
227
  <View
228
+ className="bg-surface-elevated border-border"
229
229
  style={{
230
- backgroundColor: WORKOUT_TOKENS.surface.elevated,
231
230
  borderWidth: 1,
232
- borderColor: WORKOUT_TOKENS.border.default,
233
231
  ...borderRadius,
234
232
  ...supersetMargin,
235
233
  }}
@@ -249,15 +247,15 @@ function ExpandedCard({
249
247
  paddingHorizontal: 14,
250
248
  paddingBottom: 10,
251
249
  borderBottomWidth: 1,
252
- borderBottomColor: WORKOUT_TOKENS.border.default,
250
+ borderBottomColor: resolveColor('border-default'),
253
251
  }}
254
252
  >
255
253
  <Text
254
+ className="text-text-primary"
256
255
  style={{
257
256
  fontSize: 14,
258
257
  fontFamily: '"Space Grotesk", sans-serif',
259
258
  fontWeight: '700',
260
- color: COLORS.textPrimary,
261
259
  }}
262
260
  testID="exercise-card-name"
263
261
  >
@@ -266,10 +264,10 @@ function ExpandedCard({
266
264
  <View className="flex-1" />
267
265
  {summary && (
268
266
  <Text
267
+ className="text-text-secondary"
269
268
  style={{
270
269
  fontSize: 12,
271
270
  fontFamily: 'Inter, sans-serif',
272
- color: COLORS.textSecondary,
273
271
  marginRight: 8,
274
272
  }}
275
273
  testID="exercise-card-summary"
@@ -279,7 +277,7 @@ function ExpandedCard({
279
277
  )}
280
278
  {e1rm && (
281
279
  <WeightBadge
282
- value={e1rm.value}
280
+ value={roundWeight(e1rm.value)}
283
281
  unit={e1rm.unit}
284
282
  size="sm"
285
283
  showIcon={false}
@@ -309,7 +307,7 @@ function ExpandedCard({
309
307
  }}
310
308
  testID="exercise-card-column-headers"
311
309
  >
312
- {COLUMN_HEADERS.map((header, i) => {
310
+ {columnHeaders.map((header, i) => {
313
311
  const widths = [36, undefined, 44, 56, 36]
314
312
  const width = widths[i]
315
313
  const isFlex = width === undefined
@@ -326,13 +324,13 @@ function ExpandedCard({
326
324
  {...(isFlex ? { className: 'flex-1' } : {})}
327
325
  >
328
326
  <Text
327
+ className="text-text-tertiary"
329
328
  style={{
330
329
  fontSize: 10,
331
330
  fontWeight: '600',
332
331
  fontFamily: 'Inter, sans-serif',
333
332
  textTransform: 'uppercase',
334
333
  letterSpacing: 0.8,
335
- color: COLORS.textTertiary,
336
334
  }}
337
335
  >
338
336
  {header}
@@ -376,11 +374,11 @@ function UpcomingCard({
376
374
  >
377
375
  <View className="flex-row items-center">
378
376
  <Text
377
+ className="text-text-primary"
379
378
  style={{
380
379
  fontSize: 14,
381
380
  fontFamily: '"Space Grotesk", sans-serif',
382
381
  fontWeight: '700',
383
- color: COLORS.textPrimary,
384
382
  }}
385
383
  testID="exercise-card-name"
386
384
  >
@@ -388,10 +386,10 @@ function UpcomingCard({
388
386
  </Text>
389
387
  {prescription && (
390
388
  <Text
389
+ className="text-text-secondary"
391
390
  style={{
392
391
  fontSize: 12,
393
392
  fontFamily: 'Inter, sans-serif',
394
- color: COLORS.textSecondary,
395
393
  marginLeft: 8,
396
394
  }}
397
395
  testID="exercise-card-prescription"
@@ -402,10 +400,10 @@ function UpcomingCard({
402
400
  <View className="flex-1" />
403
401
  {previousBest && (
404
402
  <Text
403
+ className="text-text-tertiary"
405
404
  style={{
406
405
  fontSize: 11,
407
406
  fontFamily: 'Inter, sans-serif',
408
- color: COLORS.textTertiary,
409
407
  }}
410
408
  testID="exercise-card-previous-best"
411
409
  >