Skip to content

feat(watches): show the carrier's expected delivery date - #22

Merged
Aswinmcw merged 1 commit into
mainfrom
feat/expected-delivery
Aug 28, 2026
Merged

feat(watches): show the carrier's expected delivery date#22
Aswinmcw merged 1 commit into
mainfrom
feat/expected-delivery

Conversation

@Aswinmcw

Copy link
Copy Markdown
Member

Blue Dart and Delhivery both publish an expected delivery date, and the public track page has always displayed it — but a watch never stored it, so the dashboard and the alert emails couldn't. This persists it and surfaces it in both places.

Changes

migrations/0010watches.estimated_delivery (TEXT, nullable). Stored as the carrier renders it ("30 Aug 2026") rather than parsed into a date: each carrier formats it differently and it is only ever displayed verbatim, exactly as src/app/track/.../page.tsx already does.

Poller — refreshes it on every successful poll, not only when a scan changes. Carriers revise the date without adding an event, so tying the write to status changes would leave a stale date sitting on the dashboard for days. It is written only when the carrier actually returned one, so a single flaky scrape can't wipe a date we already know — bluedart.ts is an HTML scraper, so that failure mode is real.

Alert email — an "Expected delivery" row in the shipment card, plus a line in the plain-text part. Falls back to the stored value when the poll that triggered the alert didn't return one.

Dashboard — a muted line under the status pill:

in transit
expected 30 Aug 2026

mirroring the existing "auto-removes in N days" note, and hidden once a shipment is delivered or cancelled, where an expected date is only noise. Deliberately not a new column — the table is already 7 wide inside a 960px container and this file has a history of layout fixes (c52ce4a, 9dd0d90).

Shiprocket publishes no ETA, so those watches simply show nothing.

Verification

Email template, compiled and rendered directly:

PASS  html shows the Expected delivery label
PASS  html shows the date
PASS  text shows it too
PASS  subject unchanged by the ETA change
PASS  no ETA -> label absent from html
PASS  no ETA -> label absent from text
PASS  no ETA -> email still renders
PASS  null ETA treated as absent
PASS  ETA is html-escaped

Dashboard, rendered for real against local D1 with three seeded watches:

active WITH eta      row_found=True   shows_eta=True
  ...21101427141 bluedart In flight in transit expected 30 Aug 2026 Every 15 minutes...
active WITHOUT eta   row_found=True   shows_eta=False
completed WITH eta   excluded (finished watches hide the ETA)

tsc --noEmit clean, poller bundles at 50.35 KiB, opennextjs-cloudflare build green.

Rollout

Migration 0010 is already applied to local and remote D1, ahead of this landing, so the column exists before any code reads or writes it. Adding it was additive and nullable; existing rows read NULL and backfill on their next poll.

Two halves land at different times, because the poller's production deploy is still disabled (its "Deploy default branch" trigger runs wrangler versions upload instead of wrangler deploy):

  • dashboard — live as soon as this merges and the web worker deploys, but the column is NULL for every existing watch until a poller carrying this change writes to it
  • ETA in emails, and populating the column — needs the poller deployed: either restore that trigger's deploy command to npx wrangler deploy -c wrangler.poller.jsonc, or run npm run deploy:poller once

Blue Dart and Delhivery both publish an expected delivery date, and the
public tracking page has always shown it — but a watch never kept it, so the
dashboard and the alert emails didn't. Persist it and surface it in both.

- migration 0010 adds watches.estimated_delivery (TEXT, nullable). Stored as
  the carrier renders it rather than parsed into a date: each carrier formats
  it differently and it is only ever displayed verbatim, exactly as the track
  page already does.
- the poller refreshes it on every *successful* poll, not only when a scan
  changes — carriers revise the date without adding an event, so tying it to
  status changes would leave a stale date on the dashboard for days. It is
  only written when the carrier actually returned one: a single flaky scrape
  can't wipe a date we already know (bluedart.ts is a scraper, so that
  matters).
- the alert email gains an "Expected delivery" row in the shipment card, and
  a line in the plain-text part. It falls back to the stored value when the
  poll that triggered the alert didn't return one.
- the dashboard shows it as a muted line under the status pill, mirroring the
  existing "auto-removes in N days" note, and hides it once a shipment is
  delivered or cancelled where an expected date is just noise. Deliberately
  not a new column: the table is already 7 wide in a 960px container and has
  a history of layout fixes.

Shiprocket exposes no ETA, so those watches simply show nothing.

Verified: email template rendered with / without / null ETA (row and text
line present only when set, and html-escaped); dashboard rendered against
local D1 with three seeded watches — active-with-ETA shows "expected 30 Aug
2026" after its status, active-without shows nothing, and the finished one is
excluded. Migration applied to local and remote D1 ahead of this landing.
tsc clean, poller bundles at 50.35 KiB, web build green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Aswinmcw
Aswinmcw requested review from a team and Aswin-coder as code owners August 28, 2026 09:36

@Aswincloud-Bot Aswincloud-Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: @Aswinmcw is a member of @Aswincloud/admins.

@Aswinmcw
Aswinmcw added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 6c6836c Aug 28, 2026
4 of 5 checks passed
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.

3 participants