fix(map): render every position fix + paginate full history (#3791)#3795
Conversation
Position history dots were subsampled to 30 markers via maxArrows, so a longer tail thinned the dots out (only the line stayed dense). And the server returned only the newest 1500 telemetry rows (~300 fixes) in a single request, so older history was never fetched. - generatePositionHistoryArrows: render a CircleMarker at EVERY fix. Heading triangles stay subsampled to maxArrows to avoid clutter on dense/stationary trails; the dots underneath are the full-resolution interactive layer. - Add an optional `before` cursor to GET /api/nodes/:nodeId/position-history (threaded through getPositionTelemetryByNodeAsync -> repo as a `lt(timestamp, before)` condition). The 1500-row per-request cap stays. - App.tsx now walks the entire history backward in bounded pages, using the oldest fix of each page as the next `before` cursor and appending progressively. Strict `<` on the oldest complete fix re-assembles any boundary fix split by the row cap without duplicates. Includes a no-progress guard, MAX_PAGES bound, gentle pacing, and cancellation on node change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011NnNR1gD45VxgB249xaTS4
|
Claude finished @Yeraze's task —— View job PR Review: fix(map): render every position fix + paginate full history
Good fix for a real user-facing bug. The logic is correct and the test coverage is solid. A few things worth examining before merge: Issues / Bugs1. // @deprecated facade
async getPositionTelemetryByNodeAsync(...): Promise<DbTelemetry[]> {
return this.telemetry.getPositionTelemetryByNode(...) // ← returns Promise<DbTelemetry[]> (async repo method)
}The underlying 2. The 3. No-progress guard has an off-by-one edge case ( if (beforeCursor !== undefined && oldest >= beforeCursor) break; // no-progress guardThe cursor for the next page is set to Performance Considerations4. accumulated.unshift(...pageItems);For large histories (50 pages × 300 fixes = 15,000 items) 5. 150 ms inter-page delay × up to 50 pages = up to 7.5 seconds ( The gentle pacing is a good idea to avoid hammering the server, but 7.5 seconds of background loading may be noticeable. This is a product decision rather than a bug — just flagging it. The state updates on each page mitigate the perceived latency. Code Quality6. async getPositionTelemetryByNodeAsync(nodeId: string, limit: number = 1500, sinceTimestamp?: number, beforeTimestamp?: number) {
return this.telemetry.getPositionTelemetryByNode(nodeId, limit, sinceTimestamp, undefined, beforeTimestamp);The fourth positional argument to 7. const MAX_PAGES = 50; // safety bound (~15k fixes)The comment says ~15k fixes but 50 pages × 1500 rows × ~1/5 ratio (5 telemetry types per fix) = 50 × 300 = 15,000 fixes. The math checks out — just be aware that 1500 telemetry rows is the server cap, not 1500 fixes, since each fix produces 5 rows (lat/lon/alt/speed/track). Test CoverageThe new tests are well-written and exercise the right things:
Missing test:
SummaryThe core approach is sound — cursor-based backward pagination with a safety bound, and rendering a
Everything else is minor style / future optimization. |
Summary
Position-history points were being dropped on the map: the dots were subsampled to a hard cap of 30 markers, so lengthening the tail thinned them out (only the connecting line stayed dense), and the server only ever returned the newest 1500 telemetry rows (~300 fixes) in a single request, so older history was unreachable. This PR renders a marker at every recorded fix and progressively loads the entire history in bounded pages, keeping the per-request 1500-row cap intact.
Changes
generatePositionHistoryArrows(mapHelpers.tsx): render aCircleMarker(with hover tooltip + click popup) at every fix. Decorative heading triangles remain subsampled tomaxArrowsso dense/stationary trails don't become a wall of overlapping arrows — the dots underneath are the full-resolution interactive layer.beforecursor:GET /api/nodes/:nodeId/position-historyaccepts an optionalbefore=<epoch ms>param returning only fixes strictly older than the cursor. Threaded throughgetPositionTelemetryByNodeAsync→ repogetPositionTelemetryByNodeas alt(timestamp, before)condition. The 1500-row per-request cap is unchanged; the new param is optional and positional-last, so existing callers (incl. the v1 route) are unaffected.App.tsx): walks the full history backward in bounded pages, using each page's oldest fix as the nextbeforecursor and appending to state progressively. Strict<on the oldest complete fix re-assembles any boundary fix split by the row cap without producing duplicates. Includes a no-progress guard,MAX_PAGESsafety bound, gentle inter-page pacing, and cancellation when the selected node changes.beforequery param indocs/api/API.md.Issues Resolved
Relates to #3791 (addresses Bug 1: position-history points vanish above a ~2-day tail). Bugs 2–4 in that issue (hover/click on points, implausible velocity, missing SNR on directs) are not addressed here.
Documentation Updates
docs/api/API.md: added thebeforecursor query parameter and a paginated-usage example.Testing
tsconfig.server.json; only pre-existingTelemetryChart.tsxnoise remains)getPositionTelemetryByNodehonors thebeforeTimestampcursor (strictly older)mapHelperstests: a dot renders at every fix; heading triangles stay capped atmaxArrows🤖 Generated with Claude Code