Skip to content

Commit b6ea031

Browse files
theredmooseclaude
andauthored
Fix: Firestore timestamp precision and offline read error messages (#49)
- fromFirestoreTimestamp() now includes nanoseconds in the milliseconds calculation (Math.round(nanoseconds / 1e6)) to avoid precision loss - getOperationErrorMessage() now accepts an optional context ('load'|'save') so network error messages are contextually correct for reads vs writes - Read error display in App.tsx passes context='load' for a friendlier offline message instead of showing raw Firebase error.message Co-authored-by: theredmoose <theredmoose@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bdeac8e commit b6ea031

3 files changed

Lines changed: 9 additions & 7 deletions

File tree

TODO.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
- [x] No validation for negative/zero measurements — MemberForm now validates weight > 0
7373
- [x] Missing Firebase env var validation — `src/config/firebase.ts` now throws with clear error listing missing vars
7474
- [ ] No account linking flow — users get stuck signing in with Google after creating email account with same address
75-
- [ ] No offline error handling — Firebase operations show raw errors when offline
75+
- [x] No offline error handling — read errors in App.tsx now use `getOperationErrorMessage()` with `'load'` context; network/unavailable codes produce friendly messages
7676
- [x] Year field in GearForm accepts invalid values — added submit-time validation (1980–currentYear+1); HTML min/max updated to match
7777
- [x] GearForm numeric fields (tip/waist/tail profile) use parseFloat/parseInt without isNaN guard — fixed: profile only built when all three fields parse to valid integers
7878
- [x] Foot measurement bounds not validated — MemberForm now validates 12–30 cm range with error message; HTML min/max updated
@@ -82,12 +82,12 @@
8282
- [x] No date-of-birth bounds validation — MemberForm now rejects future dates and dates > 120 years ago
8383
- [x] nordic-combi in skillLevels but not in SPORTS array — `SportSizing.tsx` now omits nordic-combi when persisting skill levels
8484
- [x] US shoe size conversion inconsistency — `sizing.ts` now uses `getShoeSizesFromFootLength()` from shoeSize service
85-
- [ ] Potential undefined skillLevel access — `skillLevels[currentSport.id]` in SportSizing has no fallback
85+
- [x] Potential undefined skillLevel access — already handled: `skillLevels` state initialised for all 6 sports with `?? 'intermediate'` fallback on line 68 of SportSizing.tsx
8686
- [x] No loading state for gear operations — gear delete/submit in App.tsx show no loading indicators; mutations now catch errors and surface a dismissible toast
8787
- [x] Race condition in useAuth — `setLoading(false)` and `setError()` now guarded by mounted ref
8888
- [ ] No email verification on signup — email accounts created without verifying address
8989
- [x] Hockey skate width thresholds (0.36, 0.40) hardcoded without source documentation — added Bauer/CCM fit guide comments to `determineSkateWidth()`
90-
- [ ] Firestore timestamp conversion drops nanoseconds (`firebase.ts:32`) — minor precision loss
90+
- [x] Firestore timestamp conversion drops nanoseconds — fixed: now includes `Math.round(nanoseconds / 1e6)` in milliseconds calculation
9191

9292
## Notes
9393
- App URL: https://gearguru-b3bc8.web.app

src/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useState } from 'react';
22

3-
function getOperationErrorMessage(err: unknown): string {
3+
function getOperationErrorMessage(err: unknown, context: 'load' | 'save' = 'save'): string {
44
const code = (err as { code?: string })?.code ?? '';
55
if (code === 'unavailable' || code === 'network-request-failed') {
6-
return 'Unable to save — check your connection and try again.';
6+
return context === 'load'
7+
? 'Unable to load data — check your connection and try again.'
8+
: 'Unable to save — check your connection and try again.';
79
}
810
if (code === 'permission-denied') {
911
return 'Permission denied. Please sign out and sign back in.';
@@ -261,7 +263,7 @@ function App() {
261263

262264
<div className="flex-1 overflow-y-auto bg-white px-6 py-6">
263265
{loading && <p className="loading">Loading...</p>}
264-
{error && <p className="error-state">Error: {error.message}</p>}
266+
{error && <p className="error-state">{getOperationErrorMessage(error, 'load')}</p>}
265267

266268
{!loading && !error && members.length === 0 && (
267269
<p className="empty-state">

src/services/firebase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async function toFirestoreTimestamp(isoString: string) {
2929
}
3030

3131
export function fromFirestoreTimestamp(timestamp: FirestoreTimestamp): string {
32-
return new Date(timestamp.seconds * 1000).toISOString();
32+
return new Date(timestamp.seconds * 1000 + Math.round(timestamp.nanoseconds / 1e6)).toISOString();
3333
}
3434

3535
function now(): string {

0 commit comments

Comments
 (0)