Skip to content

Add Now page: live-updating feed via polling - #5

Merged
CalebLovell merged 1 commit into
mainfrom
feat/now-page
Jul 23, 2026
Merged

Add Now page: live-updating feed via polling#5
CalebLovell merged 1 commit into
mainfrom
feat/now-page

Conversation

@CalebLovell

Copy link
Copy Markdown
Owner

Summary

  • New Now page: shows the single latest detection as a hero card, plus a recent-activity feed, polling every 10 seconds for updates -- no manual refresh needed.
  • Newly-arrived rows get a brief highlight animation so it's visually obvious something just came in.
  • A small reusable usePolledData hook (plain setInterval + refetch, no new dependency).

On useLiveQuery: it only exists for drizzle-orm/expo-sqlite (React Native) and PGlite (in-browser Postgres) -- both require the database and the React hook to run in the same process. Ours doesn't (birds.db lives on the Pi's filesystem behind node:sqlite; the browser is a separate network client), so there's no version of that hook that bridges the gap regardless of driver choice. Polling is the right tool for this shape of app instead, and a 10-second interval against a small, indexed, LIMIT-bounded query is negligible load even on Pi-class hardware.

Test plan

  • tsc --noEmit and biome check clean
  • npm run build succeeds
  • Verified live-update end-to-end: inserted a detection directly into the seed database while the page was open and confirmed it appeared automatically (promoted to the hero card, highlight animation, no reload) within one poll cycle

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

Drizzle's useLiveQuery only exists for drizzle-orm/expo-sqlite (React
Native) and PGlite (in-browser Postgres) -- both require the database
and the React hook to run in the same process. Ours doesn't: birds.db
lives on the Pi's filesystem behind node:sqlite, and the browser is a
separate network client, so there's no version of that hook that
bridges the gap regardless of driver choice.

Polling is the right tool for a server-DB/remote-client setup instead.
Added a small reusable usePolledData hook (setInterval + refetch, no
new dependency) and wired the Now page to it at a 10-second interval --
the underlying query is small, indexed, and LIMIT-bounded, so this is
negligible load even on Pi-class hardware.

The page shows the single latest detection as a hero card and a recent-
activity feed below; newly-arrived rows get a brief highlight animation
(flash-in) so it's visually obvious something just came in, without
needing a page reload. Verified end-to-end by inserting a detection
directly into the seed database and confirming it appeared live.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 23:40

Copilot AI 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.

Pull request overview

Adds a new “Now” route to the web UI that presents the latest BirdNET-Pi detection prominently and keeps the view up-to-date via client-side polling, with a brief visual highlight for newly arrived detections.

Changes:

  • Introduces /now page with a “latest detection” hero card and “recent activity” list.
  • Adds a reusable usePolledData hook to refetch server data on an interval.
  • Adds CSS animations for “new item” highlighting and a live indicator dot, and links the new page in the header/nav.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
web-ui/src/styles.css Adds flash-in highlight animation and live-dot pulsing indicator styling.
web-ui/src/routeTree.gen.ts Registers the new /now route in the generated route tree/types.
web-ui/src/routes/now.tsx Implements the new Now page UI and “fresh row” highlight tracking.
web-ui/src/lib/use-polled-data.ts Adds polling hook used by the Now page to refetch detections every 10s.
web-ui/src/components/Header.tsx Adds “Now” nav link to the header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +23 to +37
useEffect(() => {
let cancelled = false;

const id = setInterval(async () => {
try {
const next = await fetcherRef.current();
if (!cancelled) {
setData(next);
setLastUpdated(new Date());
}
} catch {
// A transient failure shouldn't kill polling -- just try again
// on the next tick.
}
}, intervalMs);
Comment thread web-ui/src/styles.css
opacity: 0.7;
box-shadow: 0 0 0 4px transparent;
}
}
@CalebLovell
CalebLovell merged commit 79f6904 into main Jul 23, 2026
1 check 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.

2 participants