Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

## 2026-11-09 - Main screen, first pass (Phase 3, session 1, draft)

- Phones (under 900px): the crossing results list now comes before the map, so the first thing after the controls is a wait. A starred crossing is already sorted first, so returning visitors see their own crossing's wait first. Desktop layout unchanged.
- One clock: the saved-data / refresh notice now shows its time in the visitor's zone, matching the header, instead of UTC. The duplicate "Snapshot retrieved" tile in the footer is removed; the header and the "Fetched ... ago" line already show it.
- Checks: full `npm test` (including route and border-map browser checks) passes after a prerendered build. Before/after phone screenshots captured in EN and ES. Entry bundle 62.00 KB gzip.
- Not in this pass: a "your crossing" hero, removing repeated info between map, list and crossing page, and lane cues in Standard-only sections (sessions 2-3).

## 2026-11-09 - Fresher backup data and an honest "could not load" state

- Fallback freshness: when the live CBP endpoint fails, the site now also reads the collector's committed feed through a same-origin rewrite (`/data/latest/crossings.json` -> `main/public/data/crossings.json` on GitHub). It keeps whichever valid backup is newer, the collector feed or the bundled copy. The bundled copy only changes when Vercel deploys (daily), so it could be most of a day old; the collector commits every 15 minutes. Backup readings are still labeled as fallback with their original clock.
Expand Down
11 changes: 2 additions & 9 deletions src/components/dashboard/AboutFooter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import React from 'react';
* Goal: give LLMs and SEO crawlers explicit, citable facts about this
* site's data, coverage, update frequency, and privacy model.
*/
export default function AboutFooter({ language, fetchedAt, count, direction = 'northbound' }) {
const updatedStr = fetchedAt ? new Date(fetchedAt).toLocaleString() : '—';
export default function AboutFooter({ language, count, direction = 'northbound' }) {
const isSouthbound = direction === 'southbound';

return (
Expand All @@ -32,7 +31,7 @@ export default function AboutFooter({ language, fetchedAt, count, direction = 'n
</div>
</div>

<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div className="rounded-lg border border-slate-200 dark:border-gray-800 p-3">
<div className="text-[10px] uppercase tracking-wider text-slate-500 mb-1">
{language === 'en' ? (isSouthbound ? 'Method' : 'Source') : (isSouthbound ? 'Método' : 'Fuente')}
Expand All @@ -57,12 +56,6 @@ export default function AboutFooter({ language, fetchedAt, count, direction = 'n
{language === 'en' ? 'Regular schedule' : 'Horario regular'}
</div>
</div>
<div className="rounded-lg border border-slate-200 dark:border-gray-800 p-3">
<div className="text-[10px] uppercase tracking-wider text-slate-500 mb-1">
{language === 'en' ? 'Snapshot retrieved' : 'Datos consultados'}
</div>
<div className="font-medium text-slate-900 dark:text-white text-sm">{updatedStr}</div>
</div>
</div>

<details className="rounded-lg border border-slate-200 dark:border-gray-800 bg-white/60 dark:bg-gray-900/40 p-4">
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/CrossingMapExplorer.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/components/dashboard/CrossingMapExplorer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const clock = (value, language, zone = 'UTC') => value && Number.isFinite(Date.p
timeZone: zone,
timeZoneName: 'short'
}).format(new Date(value)) : '—';
// The page header shows fetch time in the visitor's zone; the notice matches it.
const localZone = () => { try { return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; } catch { return 'UTC'; } };
const labels = {
en: {
title: 'Explore the border',
Expand Down Expand Up @@ -434,7 +436,7 @@ export default function CrossingMapExplorer({
setParams(q, {
replace: true
});
}} />{clock(selectedSnapshot?.fetched_at, language)}</label>}{unavailableInstant && <p>{es ? 'La observación solicitada no está en este archivo. Elige otra hora.' : 'The requested observation is absent from this archive. Choose another time.'}</p>}{lane !== 'passenger_standard' && <p>{t.notRetained}. <button onClick={() => onLaneChange('passenger_standard')}>{es ? 'Elegir auto estándar' : 'Choose Standard passenger'}</button></p>}</div> : !trusted && <div className="explorerNotice" role="status">{noData ? t.unavailable : offline ? es ? 'Sin conexión. Conservamos reportes anteriores.' : 'Offline. Retained reports are shown.' : fromFallback ? es ? 'Datos de respaldo guardados. Las condiciones actuales no están verificadas.' : 'Saved fallback data. Current conditions are not verified.' : success === false ? es ? 'No se pudo actualizar. Conservamos la hora y los valores del último reporte.' : 'Refresh unavailable. The last report values and original time are retained.' : es ? 'Consulta desactualizada o de hora desconocida.' : 'Stale or unknown retrieval time.'} {clock(fetchedAt, language)}</div>}
}} />{clock(selectedSnapshot?.fetched_at, language)}</label>}{unavailableInstant && <p>{es ? 'La observación solicitada no está en este archivo. Elige otra hora.' : 'The requested observation is absent from this archive. Choose another time.'}</p>}{lane !== 'passenger_standard' && <p>{t.notRetained}. <button onClick={() => onLaneChange('passenger_standard')}>{es ? 'Elegir auto estándar' : 'Choose Standard passenger'}</button></p>}</div> : !trusted && <div className="explorerNotice" role="status">{noData ? t.unavailable : offline ? es ? 'Sin conexión. Conservamos reportes anteriores.' : 'Offline. Retained reports are shown.' : fromFallback ? es ? 'Datos de respaldo guardados. Las condiciones actuales no están verificadas.' : 'Saved fallback data. Current conditions are not verified.' : success === false ? es ? 'No se pudo actualizar. Conservamos la hora y los valores del último reporte.' : 'Refresh unavailable. The last report values and original time are retained.' : es ? 'Consulta desactualizada o de hora desconocida.' : 'Stale or unknown retrieval time.'} {clock(fetchedAt, language, localZone())}</div>}
<div className="explorerWorkspace">
<div ref={frameRef} className="explorerMap bp-line" tabIndex="0" aria-describedby="map-location-note" aria-label={es ? 'Mapa. Usa los botones para acercar o arrastra para mover.' : 'Map. Use zoom buttons or drag to pan.'} onKeyDown={e => {
if (e.target !== e.currentTarget) return;
Expand Down
Loading