Repository navigation
feat(parcels): add /parcels tracker with carrier detection (WYA-236) - #1
Merged
Merged
Conversation
Saved tracking numbers live in localStorage only; the carrier is detected from the number format with an explicit first-match precedence. A server api/parcel-status route reads DHL's own tracking data and degrades to a carrier link-out when a carrier blocks or returns an unreadable page, never inventing a status. The map reuses the standalone tracker's type emoji and named-scan/time interpolation marker behaviour. Refs: WYA-236
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
The saved-item type select only showed an emoji; include the type name so the control is readable and screen-reader friendly. Refs: WYA-236
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d38168dabe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Match only affirmative completed deliveries so 'Not delivered' or 'will be delivered' do not place the marker at the destination. Draw a route only when both named endpoints resolve to coordinates, rather than substituting an intermediate scan for an unmapped destination. Refs: WYA-236
Keep mocked fetch implementations from leaking across test cases if an assertion fails early. Refs: WYA-236
Carrier status is live data; prevent intermediaries from caching the api/parcel-status response. Refs: WYA-236
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the standalone parcel tracker into Earth as
/parcelsand generalises it: the user saves their own tracking numbers in localStorage, the carrier is detected from the number's format, and the app reads status through a server route with a graceful link-out when a carrier blocks the request.Linear issue: WYA-236
Fixes WYA-236.
What changed
/parcelsroute (app/routes/parcels.tsx) — full-screen map plus status panel. On mobile the panel sits below the map; on desktop the two sit side by side. Add / list / remove numbers, per-item marker type, refresh status.app/lib/parcels.ts) — normalise then first-match precedence: DHL JJD, UPS1Z+16, Royal Mail, USPS 20–22 digits, Evri 16 digits, FedEx 12/15 digits, DHL 10 digits, then carrier-unknown. Precedence is documented in the file and tested.earth.parcels.v1inlocalStorageonly. No backend, no account, no sync.app/routes/api.parcel-status.ts+app/lib/server/parcel-status.ts) — acceptscarrier+number, reads DHL's own tracking JSON (falling back to DHL's page and schema.orgParcelDeliveryon other carriers), normalises status / events / ETA. Blocks, challenges, network failures and unreadable pages returnstate: "unavailable"with the carrier tracking link. An unknown format returnsunknown-carrierwithout attempting a fetch. No status is ever defaulted or invented.app/components/ParcelMap.tsx) — great-circle route arc, endpoint dots, interpolated marker. Named-city scans jump the marker; otherwise position interpolates between collection and estimated delivery. Marker emoji comes from the item's type: flight/parcelsrenders outside the TfL map + bottom sheet shell inroot.tsx; all other routes are untouched.pnpm test.Acceptance criteria → evidence
/parcelsloads; add persists across reload —tests/parcels-route.test.tsxadds a number, unmounts/remounts the app against the same localStorage, sees it again;tests/parcels.test.tsre-imports the storage module after saving. LiveGET /parcels→ 200.tests/parcels.test.tshas a fixture per carrier (DHL 10-digit and JJD, UPS, Royal Mail, FedEx 12/15, USPS 20/22 with94prefix, Evri), one unknown format, URL assertions per carrier, and precedence cases for 10/12/15/16/20–22 digit overlaps plusJJD/1Zearly wins.1291215726— live check on 30 Oct 2026:GET /api/parcel-status?number=1291215726&carrier=dhlreturnedstate: "unavailable",reason: "blocked",status: null, with the DHL tracking URL. DHL answered the server fetch with a 403 block page, so the route takes the issue's allowed graceful branch: the UI shows "Live status unavailable" and a "Track on DHL Express" link. No status is displayed or invented. The successful branch is covered bytests/parcel-status.test.tsusing a DHL-shaped payload, and the route/marker wiring bytests/parcels-route.test.tsx.fetchis never called; server test assertsunknown-carrierwith zero fetch calls.🚚for a moving item and persists a type change tofood(🍔)./200,/places200,/parcels200,/tflkeeps its existing 302 →/. The standalonecharlie-parcelcheckout was read only;watch.pyandshipment.jsonwere not modified.Validation
pnpm test— 53 tests passingpnpm typecheck— passingpnpm lint— passingpnpm build— passingNotes