Skip to content

feat(calculator): Runna/Strava-style UX with suggested times, VDOT badge, and time slider#63

Open
aleexwong wants to merge 3 commits intomainfrom
claude/add-race-time-predictor-boHIO
Open

feat(calculator): Runna/Strava-style UX with suggested times, VDOT badge, and time slider#63
aleexwong wants to merge 3 commits intomainfrom
claude/add-race-time-predictor-boHIO

Conversation

@aleexwong
Copy link
Copy Markdown
Owner

  • Add SUGGESTED_TIMES: contextual goal-time chips per distance preset
    (e.g. "Sub 3h", "3:30", "4:00" for Marathon) that instantly populate
    the HH:MM:SS fields and auto-calculate training paces on tap
  • Add SLIDER_RANGES: per-distance min/max/step config for the fine-tune
    slider (Marathon 2h–7h, 5K 12min–45min, etc.)
  • Add live VDOT badge (⚡ VDOT 45.2 · Intermediate) that updates as the
    user types or drags the slider – powered by Jack Daniels' formula
  • Add fine-tune slider that appears after a preset distance is chosen and
    a valid time is entered; dragging updates HH/MM/SS fields in real time
  • Auto-advance focus HH → MM → SS after 2 digits for faster entry
  • Auto-calculate when a suggested-time chip is tapped (no button press needed)
  • Track selectedPresetName in PaceCalculatorV2 so chips + slider are
    contextually shown only for known race distances

https://claude.ai/code/session_011ty7rQtakAjWbks15dqSXE

claude added 2 commits April 11, 2026 23:22
…dge, and time slider

- Add SUGGESTED_TIMES: contextual goal-time chips per distance preset
  (e.g. "Sub 3h", "3:30", "4:00" for Marathon) that instantly populate
  the HH:MM:SS fields and auto-calculate training paces on tap
- Add SLIDER_RANGES: per-distance min/max/step config for the fine-tune
  slider (Marathon 2h–7h, 5K 12min–45min, etc.)
- Add live VDOT badge (⚡ VDOT 45.2 · Intermediate) that updates as the
  user types or drags the slider – powered by Jack Daniels' formula
- Add fine-tune slider that appears after a preset distance is chosen and
  a valid time is entered; dragging updates HH/MM/SS fields in real time
- Auto-advance focus HH → MM → SS after 2 digits for faster entry
- Auto-calculate when a suggested-time chip is tapped (no button press needed)
- Track selectedPresetName in PaceCalculatorV2 so chips + slider are
  contextually shown only for known race distances

https://claude.ai/code/session_011ty7rQtakAjWbks15dqSXE
… unchanged

Notes April 2026 changes: SUGGESTED_TIMES chips, SLIDER_RANGES fine-tune
slider, live VDOT badge, auto-advance focus, updated onPresetClick signature.
Explicitly records that the save flow (handleSave → SavePlanDialog →
saveToDashboard → Firestore, incl. guest-redirect) is unchanged.

https://claude.ai/code/session_011ty7rQtakAjWbks15dqSXE
Copilot AI review requested due to automatic review settings April 12, 2026 17:34
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 12, 2026

Deploy Preview for trainpace ready!

Name Link
🔨 Latest commit 627f956
🔍 Latest deploy log https://app.netlify.com/projects/trainpace/deploys/69dbd9816892740008e6f587
😎 Deploy Preview https://deploy-preview-63--trainpace.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 50
Accessibility: 89
Best Practices: 100
SEO: 100
PWA: 60
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
trainpace Ready Ready Preview, Comment Apr 12, 2026 5:43pm

@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 12, 2026

Not up to standards ⛔

🔴 Issues 10 high · 6 medium · 1 minor

Alerts:
⚠ 17 issues (≤ 0 issues of at least minor severity)

Results:
17 new issues

Category Results
UnusedCode 3 medium
ErrorProne 3 medium
7 high
Security 3 high
CodeStyle 1 minor

View in Codacy

🟢 Metrics 45 complexity · 1 duplication

Metric Results
Complexity 45
Duplication 1

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Pace Calculator UI/UX to a more “Runna/Strava-style” workflow by adding contextual suggested-time chips, a fine-tune time slider, and a live VDOT badge that updates as inputs change.

Changes:

  • Add SUGGESTED_TIMES and SLIDER_RANGES constants to drive distance-specific chips and slider behavior.
  • Enhance RaceDetailsForm to render suggested chips, HH/MM/SS auto-advance, a conditional slider, and a live VDOT badge.
  • Update PaceCalculatorV2 to track selectedPresetName, compute liveVdot, and support auto-calc on chip selection and slider-driven time updates.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
vite-project/src/features/pace-calculator/types.ts Adds suggested-time and slider-range configuration per preset distance.
vite-project/src/features/pace-calculator/components/RaceDetailsForm.tsx Implements the new UX elements (chips, slider, VDOT badge, auto-advance).
vite-project/src/features/pace-calculator/components/PaceCalculatorV2.tsx Adds state/handlers for preset tracking, live VDOT, chip auto-calc, and slider time updates.
CLAUDE.md Documents the new UX features and updated prop signature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +181 to +184
<div
className="relative w-32 h-10 bg-blue-100 rounded-full cursor-pointer overflow-hidden flex-shrink-0"
onClick={handleUnitToggle}
>
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit toggle is implemented as a clickable

with only an onClick handler. This is not keyboard-accessible and won’t be announced correctly by assistive tech. Use a semantic (or add role="switch"/"button", tabIndex={0}, appropriate aria-checked state, and key handlers for Enter/Space).

Copilot uses AI. Check for mistakes.
Comment on lines +285 to +286
{/* ── 4. Fine-tune slider (preset distances only, after time is entered) ── */}
{sliderRange && hasValidTime && (
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fine-tune slider is shown when currentTimeSeconds > 0, but this includes invalid times (e.g. minutes/seconds >= 60). This contradicts the “valid time exists” behavior and can show a slider for an invalid input state. Consider gating on the same validity rules as validatePaceInputs (e.g. hide when errors.time is set, or explicitly check minutes/seconds < 60).

Suggested change
{/* ── 4. Fine-tune slider (preset distances only, after time is entered) ── */}
{sliderRange && hasValidTime && (
{/* ── 4. Fine-tune slider (preset distances only, after a valid time is entered) ── */}
{sliderRange && hasValidTime && !errors.time && (

Copilot uses AI. Check for mistakes.
Comment on lines +119 to 133
const handleInputChange = (e: { target: { name: string; value: string } }) => {
const { name, value } = e.target;

// Time input validation
if (["hours", "minutes", "seconds"].includes(name)) {
const numValue = value.replace(/\D/g, "");
setInputs((prev) => ({
...prev,
[name]: numValue.slice(0, 2),
}));
setInputs((prev) => ({ ...prev, [name]: numValue.slice(0, 2) }));
return;
}

setInputs((prev) => ({
...prev,
[name]: value,
}));
setInputs((prev) => ({ ...prev, [name]: value }));

// Clear errors for this field
if (errors[name as keyof FormErrors]) {
setErrors((prev) => ({
...prev,
[name]: undefined,
}));
setErrors((prev) => ({ ...prev, [name]: undefined }));
}
};
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectedPresetName is only set when a preset button is clicked, but it is never cleared when the user manually edits the distance field. This can leave suggested-time chips/slider showing for a preset even after switching to a custom distance, which conflicts with “contextually shown only for known race distances.” Clear selectedPresetName when name === "distance" changes via typing/paste (and possibly when units toggle changes distance away from the preset).

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +83
const totalSecs = timeToSeconds(inputs.hours, inputs.minutes, inputs.seconds);
if (totalSecs <= 0) return null;
const distMeters = inputs.units === "km" ? dist * 1000 : dist * 1609.34;
const vdot = calculateVdot(distMeters, totalSecs);
if (!isFinite(vdot) || vdot < 10 || vdot > 100) return null;
return Math.round(vdot * 10) / 10;
}, [inputs.distance, inputs.units, inputs.hours, inputs.minutes, inputs.seconds]);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

liveVdot is calculated for any nonzero time, even when the time fields are in an invalid state (e.g. minutes/seconds >= 60). This can display a VDOT badge that doesn’t correspond to a valid race time. Consider mirroring validatePaceInputs here (return null when minutes/seconds are >= 60 or when errors.time is present).

Suggested change
const totalSecs = timeToSeconds(inputs.hours, inputs.minutes, inputs.seconds);
if (totalSecs <= 0) return null;
const distMeters = inputs.units === "km" ? dist * 1000 : dist * 1609.34;
const vdot = calculateVdot(distMeters, totalSecs);
if (!isFinite(vdot) || vdot < 10 || vdot > 100) return null;
return Math.round(vdot * 10) / 10;
}, [inputs.distance, inputs.units, inputs.hours, inputs.minutes, inputs.seconds]);
if (errors.time) return null;
const minutes = parseInt(inputs.minutes || "0", 10);
const seconds = parseInt(inputs.seconds || "0", 10);
if (minutes >= 60 || seconds >= 60) return null;
const totalSecs = timeToSeconds(inputs.hours, inputs.minutes, inputs.seconds);
if (totalSecs <= 0) return null;
const distMeters = inputs.units === "km" ? dist * 1000 : dist * 1609.34;
const vdot = calculateVdot(distMeters, totalSecs);
if (!isFinite(vdot) || vdot < 10 || vdot > 100) return null;
return Math.round(vdot * 10) / 10;
}, [inputs.distance, inputs.units, inputs.hours, inputs.minutes, inputs.seconds, errors.time]);

Copilot uses AI. Check for mistakes.
Replaces the static VDOT badge pill with a <Link to="/vdot"> so users
can tap it to open the full VDOT calculator (What-If slider, percentile,
training zones, history) without duplicating that functionality in the
pace calculator. Adds ArrowRight affordance to signal it's interactive.

https://claude.ai/code/session_011ty7rQtakAjWbks15dqSXE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants