Building HeatSync Part 4: Removing Friction
After defining the MVP and getting the core flow working, I had a functional tool. Upload a PDF, enter a name, see the events. It worked. But using it felt clunky.
Every extraction required:
- Go to the meet website
- Find the heat sheet link
- Download the PDF
- Switch to HeatSync
- Upload the PDF
- Enter swimmer name
- Wait
- Download the .ics file
That’s seven steps of friction. Each step is a chance for the user to give up.
The Insight
Heat sheets are always posted on meet websites. Always. Meet coordinators upload them to a team management platform (SwimTopia, TeamUnify, etc.), and parents get a link.
So why make users download and re-upload? The URL is right there.
URL Paste
I added a simple URL input field. Paste the heat sheet URL, enter the swimmer name, done. The backend fetches the PDF directly.
Before: Download → Switch apps → Upload → Extract
After: Paste URL → ExtractThis sounds minor. It’s not.
After adding this feature, I never used the file upload again. Not once. The URL flow is just that much better.
iOS Safari Clipboard Woes
Of course, nothing is ever simple.
iOS Safari doesn’t support navigator.clipboard.readText(). The “paste from clipboard” button that worked perfectly on desktop just didn’t work on the device where people actually use this tool (phones at the pool).
The workaround is ugly but effective:
- Try the modern clipboard API first
- If it fails (iOS Safari), focus the input field
- Show a toast: “Tap and hold, then select Paste”
Not elegant, but it works. Users on iOS can still paste; they just have to do it the old-fashioned way.
Form State Management
Another friction point: what happens after extraction?
In the first version, the form just sat there. Users would accidentally re-submit, or change the swimmer name and wonder why nothing updated.
I implemented a simple state machine:
| State | UI Behavior |
|---|---|
upload | Form enabled, ready for input |
extracting | Form disabled, spinner in button |
search | Results visible, form disabled |
When extraction finds events, the form locks and shows results. A “Start Over” button resets everything.
One edge case: what if extraction finds zero events? Originally, the form would lock in search state with an empty result (useless).
Fix: if no events found, stay in upload state so users can correct their input. Show a toast notification instead of locking them out.
Small details, but they compound.
Session Date Display
Heat sheets often span multiple days. When you’re looking at a list of events, you need to know which day each event is on.
I added session date display to each event card:
Event 12: Girls 11-12 100 Free • Saturday, Jan 18
Heat 3, Lane 4 • Seed: 1:05.32One gotcha: JavaScript date handling with timezones. Dates stored as UTC were displaying as the wrong day for users in certain timezones. Fix: force UTC display for session dates.
Mobile Responsiveness
The tool is meant to be used at the pool, on a phone. Every UI decision went through the “can I use this with wet fingers?” test.
- Large tap targets
- No tiny buttons
- Readable text at arm’s length
- Results that don’t require horizontal scrolling
TailwindCSS makes this easy with responsive prefixes (sm:, md:, etc.), but you still have to think about it deliberately.
At this point, HeatSync was pleasant to use. But there were two big problems left: performance and accuracy.
Performance came first.
This is Part 4 of a series on building HeatSync. ← Part 3: Making It Work | Part 5: The Performance Problem →