fix(web,api): tolerate null journey slices - #115
Conversation
Planned journeys crashed /journeys because Blockers(nil) JSON-encodes as null and NudgePanel read blockers.length. Guard journey arrays and return empty blockers as []. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5b104204-65b7-4d04-90a4-e2d5897b8e2e
There was a problem hiding this comment.
🟢 Approval recommended
The changes directly address the nil-slice/null-array crash at both the API source and UI consumption points, and include a focused regression test for the reported failure mode.
Pull request overview
This PR fixes a /journeys frontend crash caused by Go encoding nil slices as JSON null (notably Blockers(nil)), and hardens the journeys UI + hooks against nullable slice fields so planned journeys still render.
Changes:
- Backend:
Blockers(nil)now returns an empty slice so JSON encodes[]instead ofnull. - Frontend: journey-related panels/tables and
useJourneyhooks normalize nullable arrays viasafeArrayto avoid.length/.mapcrashes. - Tests: adds a regression test ensuring
JourneyPanelrenders when detail/nudge/checklist payloads containnullslices.
File summaries
| File | Description |
|---|---|
| web/src/features/trips/components/StopScoreTable.tsx | Normalizes evidence and stops to arrays before rendering. |
| web/src/features/trips/components/StopScorePanel.tsx | Normalizes oracle sites + score result stops with safeArray. |
| web/src/features/trips/components/ReportPanel.tsx | Prevents crashes when report.evidence is null. |
| web/src/features/trips/components/ReplanPanel.tsx | Prevents crashes when assessment/result evidence/stops are null. |
| web/src/features/trips/components/NudgePanel.tsx | Prevents crashes when blockers/evidence are null. |
| web/src/features/trips/components/LiveTripPanel.tsx | Prevents crashes when trail/evidence are null. |
| web/src/features/trips/components/JourneyPanel.tsx | Normalizes sessions, next_statuses, and plans to arrays before iterating/reading length. |
| web/src/features/trips/components/JourneyPanel.test.tsx | Adds regression coverage for planned journeys with null slices. |
| web/src/features/trips/components/DeparturePanel.tsx | Normalizes slots and evidence to arrays before rendering. |
| web/src/features/trips/components/ChecklistPanel.tsx | Normalizes items to an array before mapping. |
| web/src/features/trips/components/ArrivalPanel.tsx | Prevents crashes when arrival.evidence is null. |
| web/src/api/hooks/useJourney.ts | Adds select normalizers to coerce nullable slice fields to [] at the hook boundary. |
| internal/api/journey/nudge.go | Ensures Blockers(nil) produces JSON [] and updates comment accordingly. |
| internal/api/journey/nudge_test.go | Updates unit test to assert Blockers(nil) is a non-nil empty slice. |
Review details
- Files reviewed: 14/14 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
One or more custom setup steps configured for this repository failed during this Copilot code review run: Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review. Note You can configure setup steps for Copilot code review separately from Copilot cloud agent with a |
CI Frontend failed on a flake: useLastDiagnostic used getQueryData (no observer), so gcTime:0 tests could drop diagnosticKeys.last before waitFor saw the report. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5b104204-65b7-4d04-90a4-e2d5897b8e2e
|
One or more custom setup steps configured for this repository failed during this Copilot code review run: Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review. Note You can configure setup steps for Copilot code review separately from Copilot cloud agent with a |
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently address the confirmed null-slice crash across backend encoding, frontend normalization, and add a targeted regression test.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
web/src/features/trips/components/JourneyPanel.tsx:350
- safeArray(detail.plans) is evaluated twice (length check + map), which can duplicate work and double-log warnings if the backend ever sends a non-array value. Consider computing it once and reusing it for both branches.
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
audit:query-signal requires cache-only useQuery to destructure and
reference { signal } even though queryFn never runs (enabled: false).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5b104204-65b7-4d04-90a4-e2d5897b8e2e
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently address the null-slice crash across the API and UI and include a targeted regression test; only a minor performance nit was noted.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
web/src/features/trips/components/JourneyPanel.tsx:113
safeArray()returns a new empty array ([]) each render whenlistQuery.datais null/undefined (see web/src/lib/safeArray.ts). Sincesessionsis used as a dependency in theuseEffectbelow, this makes the effect run on every render while the list is empty/loading, causing unnecessary work and repeatedsetSelectedId(...)calls. Prefer depending on the raw query data and re-normalizing inside the effect so the dependency is referentially stable.
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
One or more custom setup steps configured for this repository failed during this Copilot code review run: Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review. Note You can configure setup steps for Copilot code review separately from Copilot cloud agent with a |
Vitest treated a leaked hash-scroll setTimeout as an unhandled error (document is not defined) after SettingsSearch tests unmounted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5b104204-65b7-4d04-90a4-e2d5897b8e2e
|
One or more custom setup steps configured for this repository failed during this Copilot code review run: Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review. Note You can configure setup steps for Copilot code review separately from Copilot cloud agent with a |
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently address null-slice handling across API + UI, and include focused regression tests covering the reported crash scenario.
Review details
- Files reviewed: 17/17 changed files
- Comments generated: 0 new
- Review effort level: Lite
Summary
/journeyscrashed withCannot read properties of null (reading ''length'')because a planned journey auto-opens the leave-now nudge and Go encodedBlockers(nil)as JSONnull.Changes
[]fromBlockerswhen there is no checklist runplans,next_statuses,blockers,slots,evidence,trail,items) withsafeArrayin hooks and panelsTest
go test ./internal/api/journey/npx vitest run src/features/trips/components/JourneyPanel.test.tsxcd web && npx tsc --noEmit/journeysafter the web/API images update