Refactor/date fns - #1069
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR replaces selected ChangesDate localization
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: 🟡 Moderate · up to The date refactor changes default date ranges and relative-time labels, and may render different timestamps between the server and browser; users could see an incorrect range, misleading elapsed-time text, or hydration errors. Merge should wait until these behaviors are corrected or explicitly accepted by the owner. Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Coverage Report
File CoverageNo changed files found. |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b4d4e3bc-162f-46da-8926-4951b398f3c8
📒 Files selected for processing (11)
app/components/device-detail/device-detail-box.tsxapp/components/device-detail/graph.tsxapp/components/map/layers/mobile/mobile-overview-layer.tsxapp/components/mydevices/dt/columns.tsxapp/components/ui/range-picker.tsxapp/lib/date.tsapp/routes/admin.devices._index.tsxapp/routes/admin.users._index.tsxapp/routes/device.$deviceId.edit.logs.tsxapp/routes/device.$deviceId.edit.transfer.tsxapp/routes/profile.$username.tsx
💤 Files with no reviewable changes (1)
- app/components/device-detail/graph.tsx
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| export function DatePickerWithRange() { | ||
| const { i18n } = useTranslation() | ||
| const [date, setDate] = React.useState<DateRange | undefined>({ | ||
| from: new Date(new Date().getFullYear(), 0, 20), | ||
| to: addDays(new Date(new Date().getFullYear(), 0, 20), 20), | ||
| to: new Date(new Date().getFullYear(), 1, 10), | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- current file ---'
cat -n app/components/ui/range-picker.tsx | sed -n '1,80p'
printf '%s\n' '--- recent file history ---'
git log --oneline -5 -- app/components/ui/range-picker.tsx
printf '%s\n' '--- parent version of the relevant file ---'
parent="$(git rev-parse HEAD^)"
git show "$parent:app/components/ui/range-picker.tsx" | cat -n | sed -n '1,80p'
printf '%s\n' '--- relevant diff ---'
git diff --unified=20 HEAD^ HEAD -- app/components/ui/range-picker.tsxRepository: openSenseMap/frontend
Length of output: 4674
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- commits that changed addDays usage ---'
git log --all --oneline -S'addDays' -- app/components/ui/range-picker.tsx
printf '%s\n' '--- historical range-picker versions ---'
for commit in 4dd61e5 d493050 4205b56 HEAD; do
printf '\n[%s]\n' "$commit"
git show "$commit:app/components/ui/range-picker.tsx" 2>/dev/null \
| cat -n | sed -n '1,35p' || true
done
printf '%s\n' '--- addDays references in repository ---'
rg -n 'addDays|new Date\\(.*0, 20|new Date\\(.*1, 10' --glob '!node_modules' .Repository: openSenseMap/frontend
Length of output: 5460
Preserve the original 20-day range offset.
addDays(January 20, 20) returns February 9. Set to to February 9 to preserve the previous default range.
| const dateTimeFormat = new Intl.DateTimeFormat(i18n.language, { | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
files=(
"app/components/ui/range-picker.tsx"
"app/components/map/layers/mobile/mobile-overview-layer.tsx"
"app/components/mydevices/dt/columns.tsx"
"app/routes/admin.devices._index.tsx"
"app/routes/admin.users._index.tsx"
"app/routes/device.\$deviceId.edit.logs.tsx"
"app/routes/device.\$deviceId.edit.transfer.tsx"
"app/routes/profile.\$username.tsx"
)
for file in "${files[@]}"; do
echo "===== $file ====="
wc -l "$file"
rg -n -C 4 'Intl\.DateTimeFormat|toLocale(Date|String)|dateTimeFormat|useTranslation|createdAt|updatedAt|startTime|endTime|transferExpiresAt' "$file" || true
done
echo "===== SSR/client boundaries and timezone configuration ====="
rg -n -S 'clientOnly|ClientOnly|Hydrat|timeZone|timezone|TZ=|process\.env\.TZ|defaultTimeZone|DateTimeFormat' app package.json vite.config.* remix.config.* i18n* 2>/dev/null || true
echo "===== route/component usage ====="
rg -n -S 'RangePicker|MobileOverviewLayer|admin\.devices|admin\.users|edit\.logs|edit\.transfer|profile\.' app | head -300Repository: openSenseMap/frontend
Length of output: 18711
🏁 Script executed:
#!/bin/bash
set -eu
echo "===== relevant source context ====="
sed -n '1,75p' app/components/ui/range-picker.tsx
sed -n '105,145p;430,480p' app/components/map/layers/mobile/mobile-overview-layer.tsx
sed -n '1,110p' app/components/mydevices/dt/columns.tsx
sed -n '1,75p' app/routes/admin.devices._index.tsx
sed -n '1,65p' app/routes/admin.users._index.tsx
sed -n '85,110p;160,182p' 'app/routes/device.$deviceId.edit.logs.tsx'
sed -n '135,160p;286,304p' 'app/routes/device.$deviceId.edit.transfer.tsx'
sed -n '120,170p;190,215p' 'app/routes/profile.$username.tsx'
echo "===== framework and hydration setup ====="
sed -n '1,90p' app/entry.server.tsx 2>/dev/null || true
sed -n '1,80p' app/entry.client.tsx
sed -n '1,55p' app/components/client-only.tsx
rg -n -S 'renderTo|hydrate|SSR|ssr|ClientOnly|DatePickerWithRange|MobileOverviewLayer|getColumns|DataTable' app --glob '*.{ts,tsx}' | head -250
echo "===== date/time configuration ====="
rg -n -S 'timeZone|timezone|TZ=|process\.env\.TZ|resolvedLanguage|supportedLngs|fallbackLng|i18n' . --glob '!node_modules' --glob '!build' --glob '!dist' | head -300
echo "===== standalone Intl default-time-zone probe ====="
TZ=UTC node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('UTC:', new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' }).format(d))
JS
TZ=America/Los_Angeles node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('Los_Angeles:', new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' }).format(d))
JS
TZ=UTC node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('explicit UTC:', new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium', timeStyle: 'short', timeZone: 'UTC'
}).format(d))
JS
TZ=America/Los_Angeles node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('explicit UTC:', new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium', timeStyle: 'short', timeZone: 'UTC'
}).format(d))
JSRepository: openSenseMap/frontend
Length of output: 42073
🏁 Script executed:
#!/bin/bash
set -eu
echo "===== DataTable render path ====="
sed -n '1,180p' app/components/mydevices/dt/data-table.tsx
sed -n '1,145p' 'app/routes/explore.$deviceId.tsx'
echo "===== range-picker and mobile state/output conditions ====="
rg -n -C 5 'DatePickerWithRange|popupInfo|setPopupInfo|MobileOverviewLayer|ClientOnly|useHydrated' app --glob '*.{ts,tsx}'
echo "===== date-only versus instant formatting probe ====="
node - <<'JS'
const instant = new Date('2024-01-15T23:30:00Z')
const dateOnly = new Date(2024, 0, 20)
for (const tz of ['UTC', 'America/Los_Angeles']) {
process.env.TZ = tz
console.log(tz, {
instantDate: instant.toLocaleDateString('en-US'),
instantDateTime: instant.toLocaleString('en-US'),
dateOnlyDate: dateOnly.toLocaleDateString('en-US'),
dateOnlyDateTime: dateOnly.toLocaleString('en-US'),
})
}
JSRepository: openSenseMap/frontend
Length of output: 27119
Use a deterministic time zone for SSR-rendered timestamps.
These formatters omit timeZone. Different server and browser time zones can produce different strings for the same timestamp and cause hydration mismatches. Add an explicit timeZone, or defer formatting until the client.
Apply this to:
app/components/mydevices/dt/columns.tsx:93app/routes/admin.devices._index.tsx:48app/routes/admin.users._index.tsx:50-53app/routes/device.$deviceId.edit.logs.tsx:172-174app/routes/device.$deviceId.edit.transfer.tsx:296-299app/routes/profile.$username.tsx:161-163
The range picker uses local date-only values. The mobile popup is populated by client-side map events, so neither confirms this hydration issue.
📍 Affects 8 files
app/components/ui/range-picker.tsx#L24-L28(this comment)app/components/ui/range-picker.tsx#L44-L48app/components/map/layers/mobile/mobile-overview-layer.tsx#L124-L128app/components/map/layers/mobile/mobile-overview-layer.tsx#L460-L470app/components/mydevices/dt/columns.tsx#L35-L35app/components/mydevices/dt/columns.tsx#L93-L93app/routes/admin.devices._index.tsx#L15-L15app/routes/admin.devices._index.tsx#L48-L48app/routes/admin.users._index.tsx#L15-L15app/routes/admin.users._index.tsx#L50-L53app/routes/device.$deviceId.edit.logs.tsx#L97-L97app/routes/device.$deviceId.edit.logs.tsx#L172-L174app/routes/device.$deviceId.edit.transfer.tsx#L147-L147app/routes/device.$deviceId.edit.transfer.tsx#L296-L299app/routes/profile.$username.tsx#L130-L130app/routes/profile.$username.tsx#L162-L163
Source: Linters/SAST tools
| const ONE_MINUTE_IN_S = 60 | ||
| const ONE_HOUR_IN_S = 60 * ONE_MINUTE_IN_S | ||
| const ONE_DAY_IN_S = 24 * ONE_HOUR_IN_S | ||
| const ONE_WEEK_IN_S = 7 * ONE_DAY_IN_S | ||
| const ONE_MONTH_IN_S = 4 * ONE_WEEK_IN_S | ||
| const ONE_QUARTER_IN_S = 3 * ONE_MONTH_IN_S | ||
| const ONE_YEAR_IN_S = 12 * ONE_MONTH_IN_S |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -t f -i 'date' . | head -80
printf '%s\n' '--- date symbols and references ---'
rg -n --glob '!node_modules' 'ONE_(MONTH|YEAR|QUARTER|WEEK|DAY|HOUR|MINUTE)_IN_S|app/lib/date|format.*date|relative' app tests . 2>/dev/null | head -240
printf '%s\n' '--- date.ts outline ---'
if command -v ast-grep >/dev/null 2>&1; then
ast-grep outline app/lib/date.ts
else
wc -l app/lib/date.ts
fi
printf '%s\n' '--- app/lib/date.ts ---'
cat -n app/lib/date.tsRepository: openSenseMap/frontend
Length of output: 30854
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- exact call sites ---'
rg -n --glob '!node_modules' 'dateDiffToNowInWords' .
printf '%s\n' '--- test/config files ---'
git ls-files | rg '(^|/)(package\.json|.*date.*spec|.*date.*test|vitest|jest|README|CHANGELOG|CONTRIBUTING)' | head -160
printf '%s\n' '--- app/lib/date.ts history summary ---'
git diff --stat -- app/lib/date.ts
git diff -- app/lib/date.ts | head -220
printf '%s\n' '--- behavioral boundary calculation ---'
node - <<'JS'
const MINUTE = 60;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const MONTH = 4 * WEEK;
const QUARTER = 3 * MONTH;
const YEAR = 12 * MONTH;
function bucket(ageDays) {
const diff = Math.round(ageDays * DAY);
const abs = Math.abs(diff);
if (abs < MINUTE) return ['second', -diff];
if (abs < HOUR) return ['minute', -Math.round(diff / MINUTE)];
if (abs < DAY) return ['hour', -Math.round(diff / HOUR)];
if (abs < WEEK) return ['day', -Math.round(diff / DAY)];
if (abs < MONTH) return ['week', -Math.round(diff / WEEK)];
if (abs < QUARTER) return ['month', -Math.round(diff / MONTH)];
if (abs < YEAR) return ['quarter', -Math.round(diff / QUARTER)];
return ['year', -Math.round(diff / YEAR)];
}
for (const days of [27, 27.99, 28, 29, 56, 83, 84, 335, 336, 350, 365, 366]) {
console.log(`${days} days -> ${bucket(days)[1]} ${bucket(days)[0]}`);
}
for (const days of [350, 365, 366]) {
const d = new Date(Date.UTC(2025, 0, 1));
d.setUTCDate(d.getUTCDate() - days);
const elapsed = (Date.UTC(2025, 0, 1) - d.getTime()) / 86400000;
console.log(`calendar probe: ${days} requested -> ${d.toISOString()} -> ${elapsed} elapsed days`);
}
JSRepository: openSenseMap/frontend
Length of output: 1248
Use calendar-aware differences for month and year output.
ONE_YEAR_IN_S is 336 days, so a 350-day-old timestamp is formatted as 1 year ago. Month and quarter output also uses fixed 28-day blocks. If fixed durations are intentional, document and test this policy.
|
Gotta do the code rabbit reviews still.. |
Type of Change
Implementation
Checklist
devbranchAdditional Information
date-fnsdependency with browser-native implementation #1061 date format mismatch on my devices page #1062