@titan-design/react-ui 0.10.0 → 0.11.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 (84) hide show
  1. package/dist/index.d.mts +399 -4
  2. package/dist/index.d.ts +399 -4
  3. package/dist/index.js +2295 -895
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2279 -905
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
  8. package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
  9. package/dist/pages.d.mts +1 -1
  10. package/dist/pages.d.ts +1 -1
  11. package/dist/pages.js +1974 -1740
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +1975 -1741
  14. package/dist/pages.mjs.map +1 -1
  15. package/package.json +8 -3
  16. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
  17. package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
  18. package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
  19. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
  20. package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
  21. package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
  22. package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
  23. package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
  24. package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
  25. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
  26. package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
  27. package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
  28. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
  29. package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
  30. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
  31. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
  32. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
  33. package/src/components/custom/Fatigue/README.md +93 -0
  34. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
  35. package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
  36. package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
  37. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
  38. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
  39. package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
  40. package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
  41. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
  42. package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
  43. package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
  44. package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
  45. package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
  46. package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
  47. package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
  48. package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
  49. package/src/components/custom/Fatigue/index.ts +41 -0
  50. package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
  51. package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
  52. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
  53. package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
  54. package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
  55. package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
  56. package/src/components/custom/Workout/README.md +62 -16
  57. package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
  58. package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
  59. package/src/components/custom/Workout/SessionRail.tsx +50 -13
  60. package/src/components/custom/Workout/SetRow.test.tsx +6 -6
  61. package/src/components/custom/Workout/SetRow.tsx +9 -2
  62. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
  63. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
  64. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
  65. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
  66. package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
  67. package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
  68. package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
  69. package/src/components/custom/Workout/index.ts +3 -0
  70. package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
  71. package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
  72. package/src/components/custom/charts/SetBarChart.tsx +651 -0
  73. package/src/components/custom/charts/live-rep-growth.ts +91 -0
  74. package/src/components/custom/index.ts +1 -0
  75. package/src/test/raw-color-patterns.test.ts +71 -0
  76. package/src/theme/ColorPalettes.stories.tsx +871 -0
  77. package/src/theme/ColorPrimitives.stories.tsx +300 -0
  78. package/src/theme/bar-paper.ts +59 -0
  79. package/src/theme/color-stories.coverage.test.ts +130 -0
  80. package/src/theme/color-story-kit.tsx +170 -0
  81. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
  82. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
  83. package/src/theme/ColorSystem.stories.tsx +0 -508
  84. package/src/theme/Colors.stories.tsx +0 -323
@@ -7,6 +7,7 @@ import {
7
7
  getVelocityZoneName,
8
8
  calculateVelocityLoss,
9
9
  calculateMeanVelocity,
10
+ getVelocityLossColor,
10
11
  } from './VelocityStrip'
11
12
 
12
13
  const sampleVelocities = [1.1, 0.95, 0.82, 0.68, 0.45]
@@ -29,7 +30,7 @@ describe('VelocityStrip expanded (framed chart)', () => {
29
30
  ).toBeInTheDocument()
30
31
  })
31
32
 
32
- it('renders the 3px collapsed strip when expanded is false', () => {
33
+ it('collapses to the flat state (tap-to-expand, no info row) when expanded is false', () => {
33
34
  render(<VelocityStrip velocities={sampleVelocities} expanded={false} />)
34
35
  expect(
35
36
  screen.getByLabelText('Velocity chart for set, 5 reps, tap to expand')
@@ -78,17 +79,28 @@ describe('VelocityStrip expanded (framed chart)', () => {
78
79
  })
79
80
 
80
81
  describe('scale', () => {
81
- it('peak (default) scales the tallest rep to nearly full height', () => {
82
- // maxV=1.0 -> denom 1.06 (peak headroom) -> 1.0/1.06 = 94%.
83
- render(<VelocityStrip velocities={[1.0, 0.5]} showInfo={false} />)
84
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ height: '94%' })
82
+ // Folded onto SetBarChart: bars are px value-height (not %); the bare spotlight has static
83
+ // heights (no expand animation), so we assert the taller-rep-reads-taller scaling relationship.
84
+ const barPx = (id: string) => parseFloat(getComputedStyle(screen.getByTestId(id)).height)
85
+ const bareScale = {
86
+ variant: 'expanded' as const,
87
+ showNumbers: false,
88
+ showInfo: false,
89
+ height: 60,
90
+ }
91
+
92
+ it('peak (default) scales the tallest rep taller than a smaller one', () => {
93
+ render(<VelocityStrip {...bareScale} velocities={[1.0, 0.5]} />)
94
+ expect(barPx('velocity-bar-0')).toBeGreaterThan(barPx('velocity-bar-1'))
95
+ // peak: 1.0 fills nearly the whole 60px plot.
96
+ expect(barPx('velocity-bar-0')).toBeGreaterThan(52)
85
97
  })
86
98
 
87
- it('fixed scales against the 1.15 ceiling regardless of the set max', () => {
88
- // fixed denom 1.15 -> 1.0/1.15 = 87%, 0.5/1.15 = 43%.
89
- render(<VelocityStrip velocities={[1.0, 0.5]} scale="fixed" showInfo={false} />)
90
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ height: '87%' })
91
- expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ height: '43%' })
99
+ it('fixed scales against a ceiling above the set max (bars shorter than peak)', () => {
100
+ render(<VelocityStrip {...bareScale} scale="fixed" velocities={[1.0, 0.5]} />)
101
+ // fixed ceiling 1.15 > 1.0, so the tallest bar no longer fills the plot; 0.5 is ~half of 1.0.
102
+ expect(barPx('velocity-bar-0')).toBeLessThan(56)
103
+ expect(barPx('velocity-bar-1')).toBeLessThan(barPx('velocity-bar-0'))
92
104
  })
93
105
  })
94
106
 
@@ -116,31 +128,31 @@ describe('VelocityStrip expanded (framed chart)', () => {
116
128
  })
117
129
  })
118
130
 
119
- describe('VelocityStrip mini variant', () => {
120
- it('renders the flat mini strip', () => {
121
- render(<VelocityStrip velocities={sampleVelocities} variant="mini" />)
122
- expect(screen.getByTestId('velocity-strip-mini')).toBeInTheDocument()
131
+ describe('VelocityStrip compact variant (flat resting strip)', () => {
132
+ it('renders the flat compact strip', () => {
133
+ render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
134
+ expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
123
135
  })
124
136
 
125
137
  it('does not respond to press', () => {
126
- render(<VelocityStrip velocities={sampleVelocities} variant="mini" onToggle={() => {}} />)
138
+ render(<VelocityStrip velocities={sampleVelocities} variant="compact" onToggle={() => {}} />)
127
139
  expect(screen.queryByTestId('velocity-strip-pressable')).not.toBeInTheDocument()
128
140
  })
129
141
 
130
142
  it('renders one bar per rep', () => {
131
- render(<VelocityStrip velocities={sampleVelocities} variant="mini" />)
143
+ render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
132
144
  for (let i = 0; i < sampleVelocities.length; i++) {
133
145
  expect(screen.getByTestId(`velocity-bar-${i}`)).toBeInTheDocument()
134
146
  }
135
147
  })
136
148
 
137
149
  it('has no accessibility violations', async () => {
138
- const { container } = render(<VelocityStrip velocities={sampleVelocities} variant="mini" />)
150
+ const { container } = render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
139
151
  expect(await axe(container)).toHaveNoViolations()
140
152
  })
141
153
 
142
- it('does not apply live marking', () => {
143
- render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} variant="mini" />)
154
+ it('does not apply live marking (compact is the resting form)', () => {
155
+ render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} variant="compact" />)
144
156
  expect(screen.queryByLabelText(/latest rep/)).not.toBeInTheDocument()
145
157
  })
146
158
  })
@@ -216,6 +228,94 @@ describe('calculateMeanVelocity', () => {
216
228
  })
217
229
  })
218
230
 
231
+ // Literal hex for the four VL-band tones (green-300 / amber-300 / orange-400 / red-600),
232
+ // the SAME stops FatigueMeter's VL10/VL20/VL30 default thresholds use.
233
+ const VL_GREEN = '#2ED573'
234
+ const VL_YELLOW = '#F9B415'
235
+ const VL_ORANGE = '#FF7900'
236
+ const VL_RED = '#D14343'
237
+
238
+ describe('getVelocityLossColor', () => {
239
+ it('reads green below the VL10 threshold', () => {
240
+ expect(getVelocityLossColor(0)).toBe(VL_GREEN)
241
+ expect(getVelocityLossColor(9.9)).toBe(VL_GREEN)
242
+ })
243
+
244
+ it('reads gold/amber from VL10 up to (not including) VL20', () => {
245
+ expect(getVelocityLossColor(10)).toBe(VL_YELLOW)
246
+ expect(getVelocityLossColor(19.9)).toBe(VL_YELLOW)
247
+ })
248
+
249
+ it('reads orange from VL20 up to (not including) VL30', () => {
250
+ expect(getVelocityLossColor(20)).toBe(VL_ORANGE)
251
+ expect(getVelocityLossColor(29.9)).toBe(VL_ORANGE)
252
+ })
253
+
254
+ it('reads red at and past VL30', () => {
255
+ expect(getVelocityLossColor(30)).toBe(VL_RED)
256
+ expect(getVelocityLossColor(100)).toBe(VL_RED)
257
+ })
258
+ })
259
+
260
+ describe('VelocityStrip barColor prop', () => {
261
+ // Best rep is 1.0 m/s, so per-rep loss lands exactly on the VL10/20/30 boundaries:
262
+ // 0%, 10%, 20%, 30% loss — one rep in each band.
263
+ const decliningSet = [1.0, 0.9, 0.8, 0.7]
264
+
265
+ it('defaults to loss coloring (the landed default is barColor="loss")', () => {
266
+ render(<VelocityStrip velocities={decliningSet} variant="compact" />)
267
+ // Loss from the set's own best (1.0): 0% / 10% / 20% / 30% -> green / yellow / orange / red.
268
+ expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
269
+ expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
270
+ expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_ORANGE })
271
+ expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
272
+ })
273
+
274
+ it('explicit barColor="zone" matches the default', () => {
275
+ render(<VelocityStrip velocities={decliningSet} variant="compact" barColor="zone" />)
276
+ expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
277
+ })
278
+
279
+ it('barColor="loss" colors each bar green→red by loss from the set\'s own best', () => {
280
+ render(<VelocityStrip velocities={decliningSet} variant="compact" barColor="loss" />)
281
+ expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
282
+ expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
283
+ expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_ORANGE })
284
+ expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
285
+ })
286
+
287
+ it('barColor="loss" reads a slow-but-still-best rep as green (loss-relative, not absolute)', () => {
288
+ // 0.3 m/s would be "vel-red" under the absolute zone scale, but as the set's
289
+ // (single) best rep its loss is 0 — loss-relative coloring reads it green.
290
+ render(<VelocityStrip velocities={[0.3]} variant="compact" barColor="loss" />)
291
+ expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
292
+ })
293
+
294
+ it('barColor="loss" ignores a supplied zones prop (it is a separate scale)', () => {
295
+ render(
296
+ <VelocityStrip
297
+ velocities={decliningSet}
298
+ variant="compact"
299
+ barColor="loss"
300
+ zones={compoundBands}
301
+ />
302
+ )
303
+ expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
304
+ })
305
+
306
+ it('barColor="loss" guards all-zero velocities to green (no NaN/best<=0)', () => {
307
+ render(<VelocityStrip velocities={[0, 0, 0]} variant="compact" barColor="loss" />)
308
+ expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
309
+ expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_GREEN })
310
+ })
311
+
312
+ it('renders no bars (and does not crash) for an empty velocities array', () => {
313
+ render(<VelocityStrip velocities={[]} variant="compact" barColor="loss" />)
314
+ expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
315
+ expect(screen.queryAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(0)
316
+ })
317
+ })
318
+
219
319
  // WA-shaped 5-band zone set (compound movement-class defaults, mean m/s).
220
320
  const compoundBands = [
221
321
  { id: 'grinding', label: 'Grinding', min: 0, max: 0.35 },
@@ -226,14 +326,29 @@ const compoundBands = [
226
326
  ] as const
227
327
 
228
328
  describe('VelocityStrip zones prop', () => {
329
+ // The band mapping is the `zone` scale; the landed default is `loss`, so these pass `barColor="zone"`.
229
330
  it('colors bars from the supplied bands (mini)', () => {
230
- render(<VelocityStrip velocities={[1.1, 0.45]} zones={compoundBands} variant="mini" />)
331
+ render(
332
+ <VelocityStrip
333
+ velocities={[1.1, 0.45]}
334
+ zones={compoundBands}
335
+ variant="compact"
336
+ barColor="zone"
337
+ />
338
+ )
231
339
  expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
232
340
  expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
233
341
  })
234
342
 
235
343
  it('gives grinding and maximalStrength DISTINCT reds (5-band, no collapse)', () => {
236
- render(<VelocityStrip velocities={[0.45, 0.2]} zones={compoundBands} variant="mini" />)
344
+ render(
345
+ <VelocityStrip
346
+ velocities={[0.45, 0.2]}
347
+ zones={compoundBands}
348
+ variant="compact"
349
+ barColor="zone"
350
+ />
351
+ )
237
352
  expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#D14343' })
238
353
  expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#A4221C' })
239
354
  })
@@ -244,7 +359,7 @@ describe('VelocityStrip zones prop', () => {
244
359
  })
245
360
 
246
361
  it('falls back to the default scale when zones is absent', () => {
247
- render(<VelocityStrip velocities={[1.1]} variant="mini" />)
362
+ render(<VelocityStrip velocities={[1.1]} variant="compact" />)
248
363
  expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
249
364
  })
250
365
  })
@@ -257,16 +372,17 @@ describe('VelocityStrip live mode', () => {
257
372
  else delete (window as { matchMedia?: unknown }).matchMedia
258
373
  })
259
374
 
260
- it('marks the latest rep bar in its accessibility label', () => {
261
- render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={3} />)
262
- expect(screen.getByLabelText(/Rep 4: 0\.68 meters per second, latest rep$/)).toBeInTheDocument()
375
+ // The bars are now SetBarChart's (a11y-hidden; live-grow is the visual marking via `liveRepIndex`).
376
+ const spotlight = { variant: 'expanded' as const, showNumbers: false, showInfo: false }
377
+
378
+ it('renders the newest rep in the live spotlight', () => {
379
+ render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={3} />)
380
+ expect(screen.getByTestId('velocity-bar-3')).toBeInTheDocument()
263
381
  })
264
382
 
265
- it('flags a new set peak on the latest bar', () => {
266
- render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} />)
267
- expect(
268
- screen.getByLabelText(/Rep 1: 1\.10 meters per second, latest rep, new set peak$/)
269
- ).toBeInTheDocument()
383
+ it('renders a new-set-peak live rep without crashing', () => {
384
+ render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={0} />)
385
+ expect(screen.getByTestId('velocity-bar-0')).toBeInTheDocument()
270
386
  })
271
387
 
272
388
  it('renders without animation when prefers-reduced-motion is set', () => {
@@ -275,48 +391,56 @@ describe('VelocityStrip live mode', () => {
275
391
  addEventListener: vi.fn(),
276
392
  removeEventListener: vi.fn(),
277
393
  }) as unknown as typeof window.matchMedia
278
- render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} />)
394
+ render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={0} />)
279
395
  expect(screen.getByTestId('velocity-bar-0')).toBeInTheDocument()
280
396
  })
281
397
  })
282
398
 
283
399
  describe('VelocityStrip all-zero velocities (NaN guard)', () => {
284
- it('emits a valid 0% bar height (not NaN) when every velocity is zero', () => {
285
- render(<VelocityStrip velocities={[0, 0, 0]} showInfo={false} />)
286
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ height: '0%' })
287
- expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ height: '0%' })
400
+ it('emits a valid (non-NaN) bar height when every velocity is zero', () => {
401
+ render(
402
+ <VelocityStrip
403
+ velocities={[0, 0, 0]}
404
+ variant="expanded"
405
+ showNumbers={false}
406
+ showInfo={false}
407
+ />
408
+ )
409
+ const h = getComputedStyle(screen.getByTestId('velocity-bar-0')).height
410
+ expect(h).not.toContain('NaN')
411
+ expect(parseFloat(h)).toBeGreaterThanOrEqual(0)
288
412
  })
289
413
  })
290
414
 
291
415
  // Literal-hex slot colors (must match the component's tokens exactly).
292
- const TODO_GREY = '#2C2C2C'
416
+ // TODO_SOLID = the LANDED surface-relative solid to-do tone (base/dark plane blended toward the
417
+ // on-surface neutral) — replaced the old fixed charcoal #2C2C2C. Solid fill, not a dashed outline.
418
+ const TODO_SOLID = '#4C4E55'
293
419
  const VARIABLE_CYAN = '#0B3149'
294
420
  const CONTINUE_OUTLINE = '#22465F'
295
- const CONTAINER_GAP_PX = '2px'
296
- const REP_SLOT_ML = '0px'
297
- const WIDE_SLOT_ML = '6px'
298
-
299
- describe('VelocityStrip set-type modes (mini)', () => {
300
- it('straight: done reps colored + grey todo remainder to the planned count', () => {
421
+ describe('VelocityStrip set-type modes (compact)', () => {
422
+ // compact composes SetBarChart, so the per-slot geometry (WIDE chunk-notch px, container gap) is
423
+ // covered in SetBarChart.test; here we verify VelocityStrip maps each set type to the right slots.
424
+ it('straight: done reps colored + solid todo remainder to the planned count', () => {
301
425
  render(
302
426
  <VelocityStrip
303
427
  set={{ type: 'straight', velocities: [1.1, 0.9, 0.8], planned: 5 }}
304
- variant="mini"
428
+ variant="compact"
305
429
  />
306
430
  )
307
431
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(3)
308
432
  expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(2)
309
433
  expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
310
434
  expect(screen.getAllByTestId('velocity-slot-todo')[0]).toHaveStyle({
311
- backgroundColor: TODO_GREY,
435
+ backgroundColor: TODO_SOLID,
312
436
  })
313
437
  })
314
438
 
315
- it('range: committed grey todo + a cyan variable window floor..max', () => {
439
+ it('range: committed reps + solid floor todo + a cyan variable window floor..max', () => {
316
440
  render(
317
441
  <VelocityStrip
318
442
  set={{ type: 'range', velocities: [1.1, 1.0, 0.9, 0.85, 0.8, 0.7], floor: 8, max: 12 }}
319
- variant="mini"
443
+ variant="compact"
320
444
  />
321
445
  )
322
446
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(6)
@@ -327,15 +451,15 @@ describe('VelocityStrip set-type modes (mini)', () => {
327
451
  })
328
452
  })
329
453
 
330
- it('amrap: done reps + one trailing outlined cyan continue slot', () => {
331
- render(<VelocityStrip set={{ type: 'amrap', velocities: [1.1, 1.0, 0.9] }} variant="mini" />)
454
+ it('amrap: done reps + one trailing cyan-outline continue slot', () => {
455
+ render(<VelocityStrip set={{ type: 'amrap', velocities: [1.1, 1.0, 0.9] }} variant="compact" />)
332
456
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(3)
333
- const cont = screen.getByTestId('velocity-slot-continue')
334
- expect(cont).toHaveStyle({ backgroundColor: VARIABLE_CYAN })
335
- expect(cont).toHaveStyle({ borderTopColor: CONTINUE_OUTLINE })
457
+ expect(screen.getByTestId('velocity-slot-continue')).toHaveStyle({
458
+ borderTopColor: CONTINUE_OUTLINE,
459
+ })
336
460
  })
337
461
 
338
- it('drop: sub-loads split by a WIDE gap before each load after the first', () => {
462
+ it('drop: one bar per rep across the sub-loads', () => {
339
463
  render(
340
464
  <VelocityStrip
341
465
  set={{
@@ -346,17 +470,13 @@ describe('VelocityStrip set-type modes (mini)', () => {
346
470
  [0.6, 0.5],
347
471
  ],
348
472
  }}
349
- variant="mini"
473
+ variant="compact"
350
474
  />
351
475
  )
352
476
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(6)
353
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ marginLeft: '0px' })
354
- expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
355
- expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
356
- expect(screen.getByTestId('velocity-bar-4')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
357
477
  })
358
478
 
359
- it('myo: activation + clusters split by WIDE gaps, open adds a cyan continue', () => {
479
+ it('myo: activation + clusters as reps, open adds a cyan continue', () => {
360
480
  render(
361
481
  <VelocityStrip
362
482
  set={{
@@ -368,16 +488,14 @@ describe('VelocityStrip set-type modes (mini)', () => {
368
488
  ],
369
489
  open: true,
370
490
  }}
371
- variant="mini"
491
+ variant="compact"
372
492
  />
373
493
  )
374
494
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(7)
375
- expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
376
- expect(screen.getByTestId('velocity-bar-5')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
377
495
  expect(screen.getByTestId('velocity-slot-continue')).toBeInTheDocument()
378
496
  })
379
497
 
380
- it('cluster: fixed count grouped by WIDE intra-rest gaps, grey planned remainder', () => {
498
+ it('cluster: fixed count + solid planned remainder', () => {
381
499
  render(
382
500
  <VelocityStrip
383
501
  set={{
@@ -386,22 +504,19 @@ describe('VelocityStrip set-type modes (mini)', () => {
386
504
  groupSize: 2,
387
505
  planned: 8,
388
506
  }}
389
- variant="mini"
507
+ variant="compact"
390
508
  />
391
509
  )
392
510
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(5)
393
511
  expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(3)
394
- expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
395
- expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
396
512
  })
397
513
 
398
- it('back-compat: a velocities-only mini strip is unchanged (rep colors, rep gaps only)', () => {
399
- render(<VelocityStrip velocities={sampleVelocities} variant="mini" />)
514
+ it('back-compat: a velocities-only compact strip renders rep bars, no todo', () => {
515
+ render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
400
516
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(sampleVelocities.length)
401
517
  expect(screen.queryByTestId('velocity-slot-todo')).not.toBeInTheDocument()
402
- expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ marginLeft: '0px' })
403
518
  expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
404
- expect(screen.getByTestId('velocity-strip-mini')).toHaveStyle({ gap: CONTAINER_GAP_PX })
519
+ expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
405
520
  })
406
521
 
407
522
  it('derives the summary from a set descriptor (mean / loss info row)', () => {
@@ -410,14 +525,14 @@ describe('VelocityStrip set-type modes (mini)', () => {
410
525
  })
411
526
 
412
527
  it('labels the strip per set type', () => {
413
- render(<VelocityStrip set={{ type: 'amrap', velocities: [1.0, 0.9] }} variant="mini" />)
528
+ render(<VelocityStrip set={{ type: 'amrap', velocities: [1.0, 0.9] }} variant="compact" />)
414
529
  expect(
415
530
  screen.getByLabelText('Velocity strip, AMRAP set, 2 reps and counting')
416
531
  ).toBeInTheDocument()
417
532
  })
418
533
 
419
534
  it('renders nothing when neither velocities nor set is provided', () => {
420
- const { container } = render(<VelocityStrip variant="mini" />)
535
+ const { container } = render(<VelocityStrip variant="compact" />)
421
536
  expect(container.firstChild).toBeNull()
422
537
  })
423
538
  })
@@ -428,7 +543,7 @@ describe('VelocityStrip set-type modes (expanded framed)', () => {
428
543
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(2)
429
544
  expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(2)
430
545
  expect(screen.getAllByTestId('velocity-slot-todo')[0]).toHaveStyle({
431
- backgroundColor: TODO_GREY,
546
+ backgroundColor: TODO_SOLID,
432
547
  })
433
548
  })
434
549
 
@@ -459,8 +574,8 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
459
574
  set={{ type: 'straight', velocities: [1.0, 0.8], planned: 2 }}
460
575
  />
461
576
  )
462
- expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
463
- expect(screen.queryByTestId('velocity-strip-mini')).not.toBeInTheDocument()
577
+ expect(screen.getByTestId('velocity-strip-spotlight')).toBeInTheDocument()
578
+ expect(screen.queryByTestId('velocity-strip-compact')).not.toBeInTheDocument()
464
579
  expect(screen.queryByTestId('velocity-strip')).not.toBeInTheDocument()
465
580
  })
466
581
 
@@ -500,7 +615,8 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
500
615
  expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(1)
501
616
  const stubs = screen.getAllByTestId('velocity-slot-todo')
502
617
  expect(stubs).toHaveLength(2)
503
- expect(stubs[0]).toHaveStyle({ height: '3px', backgroundColor: TODO_GREY })
618
+ // Folded onto SetBarChart: the solid to-do stub is the shared 9px height + surface-relative tone.
619
+ expect(stubs[0]).toHaveStyle({ height: '9px', backgroundColor: TODO_SOLID })
504
620
  })
505
621
 
506
622
  it('colors performed reps from the default effort scale', () => {
@@ -514,7 +630,7 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
514
630
  expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
515
631
  })
516
632
 
517
- it('carries the set-type WIDE-gap chunk encoding (drop sub-loads)', () => {
633
+ it('carries the set-type chunk encoding (drop sub-loads) as one bar per rep', () => {
518
634
  render(
519
635
  <VelocityStrip
520
636
  {...bareProps}
@@ -527,8 +643,9 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
527
643
  }}
528
644
  />
529
645
  )
530
- expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
531
- expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
646
+ // Folded onto SetBarChart: 4 rep bars; the WIDE chunk-notch (now a proportional column margin)
647
+ // is covered in SetBarChart.test.
648
+ expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(4)
532
649
  })
533
650
 
534
651
  it('has no chrome: no per-bar labels, no info row, no pressable', () => {
@@ -576,7 +693,7 @@ describe('VelocityStrip hero variant', () => {
576
693
  expect(screen.getByTestId('velocity-label-0')).toHaveTextContent('0.90')
577
694
  })
578
695
 
579
- it('draws a dashed placeholder for each rep still to come (target − done)', () => {
696
+ it('draws a to-do placeholder for each rep still to come (target − done)', () => {
580
697
  render(<VelocityStrip velocities={heroSet} variant="hero" targetReps={8} />)
581
698
  expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(4)
582
699
  })