Skip to content

Add an all-time period to History - #99

Merged
2 commits merged into
blike:mainfrom
MrSerious0:feat/history-all-time
Nov 9, 2026
Merged

2 commits merged into
blike:mainfrom
MrSerious0:feat/history-all-time

Conversation

@MrSerious0

Copy link
Copy Markdown
Contributor

Summary

History's max time period is currently 12 months. This adds an All time period, whose window runs
from the first drive to now.

Changes

  • options.ts — an all period (bucket: '6h', no fixed length) with isAllTime(), and
    prefetchRanges(), which leaves it out of what loads ahead. Six hours a point, because day buckets are
    cut in UTC and would each land on the wrong local day.
  • history.ts — rangeWindow() takes the first drive, so a period of no fixed length starts there.
    Until it is known the window is empty; a since that does not parse, or one from ahead of us, is
    treated as unknown rather than throwing while the page renders. distanceByPeriod() gains a calendar
    month.
  • HistoryPage.tsx — reads the first drive before computing the window, and waits for it before
    asking anything: the three queries that key on the period alone would otherwise cache an empty answer
    under the key the view then uses, and the drives and sessions would ask for a zero-width window. If
    that read fails with nothing loaded, the page says so above the cards, with a retry. The header reads
    "All time".
  • SummaryCard.tsx — a bar a month past two years, chosen from the window's length rather than the
    period's name, and month labels that carry the year.
  • Timeline.tsx — its chart waits for the window before loading.

Testing

  • Unit tests added/updated — rangeWindow for all time with, without, with an unparseable first
    drive and with one from ahead of us; month buckets including a quiet month between two drives;
    prefetchRanges in both directions, and that all time ends the period list.
  • Integration tests passed — not run: no schema, migration or write path here, and CI skips them
    without TEST_DATABASE_URL.
  • Manually tested — demo mode, the dropdown entry and the page under All time (screenshots below).
  • Existing tests passed — web 403, server 479 (217 database tests skipped); format:check,
    typecheck, lint and build clean.

Deployment

  • No special deployment requirements

Screenshots

Demo mode (MOCK_RIVIAN=1), whose history starts six days ago.

The period dropdown, with the new entry last:

The period dropdown

History under All time — the header and the period selector both read it, and the figures are the
seven days', since the window starts at the first drive:

History under All time

The vehicle-state strip and the battery line cover only the running session, because the demo scene seeds
drives and charges but records state live.

The month bars, which need more than two years and so cannot be reached from the committed scene: a
local database seeded with three years of synthetic drives. Its timeline axis repeats labels at this
range, which predates this change. No real vehicle data:

Three years, by month

Additional Notes

  • Naming: "All time" rather than "Lifetime", because RivianMate's data feed likely started after the
    car's first drive.
  • What the window spans: the first drive. useDriveCoverage is the anchor the page already had for
    the energy ledger; a vehicle whose charging predates its first drive has that charging outside the
    window.
  • Long histories and the existing limits: /drives and /charging-sessions each return at most
    5,000 rows when given a window, and they keep the newest. Past that, an all-time summary undercounts and
    the earliest bars draw empty. Unchanged by this PR — the same cap applies to 12 months — but all time
    reaches it sooner.
  • Unbounded reads in the app's own database: /vehicles/:id/locations selects every point in the
    window and thins afterwards, and /history loads every change before bucketing, so bucket bounds the
    response and not the read. TeslaMate does the bucketing and the row cap in SQL, so this is Rivian's
    path. /charge-spans and /state-spans read the same way, but consecutive changes collapse into spans,
    so far fewer rows come back. I have not measured an all-time window against a multi-year history; mine
    is nine days old.
  • What six hours costs: four points a day instead of one, so a five-year history is about 7,300 points
    per metric request against 1,825. Response size only — the read is the same either way.
  • CHANGELOG: user-facing, so a line at release — "An all-time period on History".
  • No server change: /history takes an unbounded from and already accepted 6h and 1d buckets.

History's longest period is 12 months, so a longer record cannot be seen
at all. This adds an "All time" period whose window runs from the first
drive to now, and nothing else: the window comes from the coverage the
page already loads, and a fixed-length-less period is drawn six hours a
point and a month a bar past two years.

Two things had to move for it. The queries that key on the period alone
wait for the window, or an empty answer would be cached under the key the
view then uses; and neither prefetch loads all time, whose window is the
whole history. If the coverage read fails with nothing loaded, the page
says so above the cards with a retry.
All time is the whole history, so polling it every minute re-read every
drive, session, span and location point each time. Its queries now load
when the period is picked and stay fresh for five minutes, so switching
away and straight back doesn't load it again.

The map's frame and the timeline's value range spread every point into
Math.min and Math.max, and a long history has more points than a call
takes arguments: the page threw "Maximum call stack size exceeded". Both
now use a loop.
@blike

blike commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Thanks for this. I added one commit on top, 3a22241:

  • All time loads once. It's the whole history, so it no longer polls every minute. Switching away and back within five minutes reuses the load.
  • No stack overflow on long histories. The map's frame and the timeline's range spread every point into Math.min/Math.max, which threw "Maximum call stack size exceeded" past ~100k points. Both now loop.

The notes about the 5,000-row limit are out of date: #104 removed it. The axis labels are fixed in #105 and the long-range map in #106. All three land together.

blike added a commit that referenced this pull request Nov 9, 2026
Merges the all-time period (#99) and the time axis fix (#105) into the
map routes branch, so History's changes ship in one merge to main.
@blike blike closed this pull request by merging all changes into blike:main in e097f29 Nov 9, 2026
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.

2 participants