Chart-position watchdog + browser notifications#2
Merged
Conversation
Daily Queues job that polls Apple's iTunes RSS top-free chart for each watched app's primary genre across the storefronts where the app is available, and emits ChartEvent rows on every transition (entered / moved / exited). The watchdog stays silent for never-charted apps — Azri isn't in any chart's top-100 today but will be eventually, and the dashboard wants to know the moment that changes. - Three new tables: app_storefront_availability, chart_position_snapshot, chart_event. Snapshot rows hold only the latest position per (app, country, chart, genre) tuple so storage stays bounded. - WatchedApp gains primary_genre_id (backfilled by AvailabilityProber on next probe or lazily by ChartTrackerService on first refresh). - AvailabilityProber: batched iTunes lookup against all 175 storefronts; triggered from AppsController.create and POST /apps/:id/availability/refresh. - ChartTrackerService: per-(app, country) Fluent transaction wraps each diff so an interrupted job can't desync the snapshot from the event log. Diff logic extracted into a pure `decideChartTransition` for unit testing. - ITunesChartsClient: thin wrapper around the legacy /<cc>/rss/topfreeapplications/limit=200/genre=<id>/json endpoint (the v2 applemarketingtools API doesn't support a genre filter). Static `parseEntries` exposed for tests. - RefreshChartsScheduler runs daily at 04:00 UTC (an hour after the keyword refresh and a few hours past Apple's PT-midnight chart refresh window so the RSS feeds have settled). POST /charts/refresh spawns the same work via a detached Task. - Four new routes: GET /chart-positions, GET /chart-events?since=&limit=, POST /charts/refresh, POST /apps/:id/availability/refresh. - Tests cover all 7 transition cases in the diff matrix plus the RSS envelope decoder.
Surfaces the chart-watchdog from the backend. A new "Charts" button in the dashboard toolbar (with an unread-count badge fed by a 30s polling loop) opens a full-screen ChartsPage with three states: - Empty + permission CTA: first visit asks for Notification permission; the Later button is remembered via localStorage so we don't re-prompt. - Currently charted: cards showing rank + flag + country + genre + "seen X ago" for each non-null snapshot row. - Recent activity: feed of entered/moved/exited events with relative timestamps and a glyph per transition kind. A "Check now" button POSTs to /api/v1/charts/refresh and reloads after the backend job settles; "Re-probe availability" loops over watched apps and kicks /apps/:id/availability/refresh for each. chartEvents.ts owns the singleton 30s poll, deduping by event id and persisting lastSeenIso so reloads never re-fire stale notifications. notifications.ts wraps the Notification API with a graceful fallback when the user denies or the browser doesn't support it — the activity feed remains the durable record. Genre IDs decoded inline (small static map) since the backend stores the numeric id; only common App Store categories are hardcoded, the rest fall back to "Category #N".
Both directories hold per-session local state (Claude Code's launch config, the brainstorming companion's HTML mockups) and shouldn't be committed. Silences the 'uncommitted changes' warning gh kept emitting on push and pr create.
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
A daily background job that polls Apple's free iTunes RSS top-free charts for each watched app's primary genre across the storefronts where the app is available, persists what changed, and pushes browser notifications to any open dashboard tab. New `Charts` toolbar button opens a full-screen page with the Currently charted cards and a Recent activity feed.
Designed during a brainstorm session — the spec lives at `/Users/astemirboziev/.claude/plans/in-the-modal-for-validated-conway.md`. Key choices:
Architecture
```
Apple iTunes RSS ──┐
▼
RefreshChartsScheduler (daily, 04:00 UTC)
│
▼
ChartTrackerService ── diff vs chart_position_snapshot ── emit ChartEvent
│
▼
GET /api/v1/chart-events?since=… ◀── SPA polls every 30s ──── new Notification(…)
```
Backend additions:
Frontend additions:
Diff algorithm
Pure-function form (`decideChartTransition`) extracted so all 7 cases are unit-tested without a DB.
Test plan
Follow-ups (out of scope)