static: public Reverse Watch dashboard#71
Conversation
Single self-contained static/index.html (no build step) plus the icons / favicons / chart-event data it consumes. Layout - three KPI cards (Traders indexed, Traders flagged, Traders flagged 24h) - volume chart with period picker (7d / 30d / 3m / 6m / 1y), powered by uPlot from the unpkg CDN - recent-reversals table with client-side paging - single-Steam-ID search with avatar + display name + Steam/CSFloat profile link result chip - CS2 event annotation chips on the chart, data driven by static/cs2-events.json — edit that file to add or update events - responsive mobile layout (single-column, condensed KPI row) Dependencies - Material Symbols (Google Fonts CDN) - uPlot 1.6.x (unpkg CDN) - No bundler, no NPM dependencies, no build step. This PR depends on #1 (in-memory static serving) and #2 (public stats / recent endpoints) being merged for the dashboard to function, but is reviewable in isolation against master. Co-authored-by: Cursor <cursoragent@cursor.com>
|
If you want to make this code easier to maintain: there are lots of Static Site Generators (SSGs) to keep the build step super minimal. I can recommend:
Probably max. a 2-prompt addition to this PR. All of these can be configured to generate fully static HTMLs at build-time. |
There was a problem hiding this comment.
We can inline this as a const in the script block in our html file.
There was a problem hiding this comment.
Done — inlined as a CS2_EVENTS const in the script block; the JSON file is removed.
| // Dev placeholder: deterministic fake Steam display names derived | ||
| // from steam_id. Remove when real names ship (PRD §14 D-open-4). | ||
| const TRADER_ADJECTIVES = [ | ||
| 'Shadow', 'Golden', 'Crimson', 'Frozen', 'Stealth', 'Iron', 'Silent', | ||
| 'Quick', 'Rogue', 'Mighty', 'Phantom', 'Toxic', 'Wild', 'Lone', 'Lucky', | ||
| 'Cosmic', 'Royal', 'Savage', 'Mystic', 'Dark', 'Vivid', 'Solar', 'Lunar', | ||
| 'Electric', 'Neon', 'Cyber', 'Mecha', 'Ghost', 'Steel', 'Radiant', | ||
| ]; | ||
| const TRADER_NOUNS = [ | ||
| 'Wolf', 'Hawk', 'Tiger', 'Fox', 'Bear', 'Dragon', 'Phoenix', 'Viper', | ||
| 'Hunter', 'Sniper', 'Ranger', 'Knight', 'Reaper', 'Warden', 'Mage', | ||
| 'Trader', 'Ace', 'Bandit', 'Ninja', 'Samurai', 'Pirate', 'Demon', | ||
| 'Spirit', 'Storm', 'Blade', 'Shadow', 'Wraith', 'Beast', 'Falcon', 'Specter', | ||
| ]; | ||
| const TRADER_SUFFIXES = [ | ||
| '', '', '', '_HD', '47', '88', '99', '_TR', 'X', 'TV', '_GG', '_RU', 'Z', '03', '13', | ||
| ]; |
There was a problem hiding this comment.
Please remove all placeholder data. In general, this should not be committed.
There was a problem hiding this comment.
Done — removed the fake display-name generator entirely. The Trader column + chip now show the real Steam ID, and avatars are a neutral person glyph. Real display names are pending a source decision (the open Steam-name-source question). Done in 8a04e6b.
| # [reverse.watch](https://reverse.watch) | ||
|
|
||
| Community-driven open trade reversal tracking database for Steam. Participating entities can report trade reverals to the open database. | ||
| Community-driven open trade reversal tracking database for Steam. Participating entities can report trade reversals to the open database, and anyone can browse activity at [reverse.watch](https://reverse.watch) — a public dashboard served from [`static/index.html`](static/index.html) at `/`. |
There was a problem hiding this comment.
Please remove all changes, except for the spelling fix.
There was a problem hiding this comment.
Done — the README diff vs master is now just the reverals to reversals spelling fix.
This looks like a great idea. Missed this comment in my original review. We can use Astro, it's the lighter-weight option. We can use a FileServer which exposes certain directories This will require a Node build stage in the Dockerfile. fs := http.FileServer(http.Dir("dist"))
r.Handle("/static/*", fs)
r.Handle("/_astro/*", fs)
r.Get("/", func(w, r) { http.ServeFile(w, r, "dist/index.html") })Here's a rough outline of the structure: |
- Anchor the chart fill gradient to the plot area (u.bbox.top .. u.bbox.top + u.bbox.height) so the fade aligns with the plotted line instead of the canvas origin. - Keep Steam IDs as strings end-to-end: they are uint64 and exceed Number.MAX_SAFE_INTEGER, so coercing them through Number would corrupt the profile/stall links and table rows. The API already marshals steam_id as a JSON string; explicitly String() it at the chip links and recent-table rows so it is never parsed as a number. - Inline the CS2 event annotations as a CS2_EVENTS const in the script block, drop the runtime fetch of /static/cs2-events.json, and delete the now-unused static/cs2-events.json file. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the single self-contained static/index.html with an Astro project in web/ that builds to web/dist (gitignored). uPlot is now a pinned npm dependency bundled into the hashed _astro output instead of loaded from a CDN; Material Symbols stays on its existing web-font link. Markup is split into Hero/Search/Kpis/ReversalChart/RecentReversals/ SiteFooter components, the inline <style> moves to src/styles/global.css, and the inline <script> (including the CS2_EVENTS const and string-only steam_id handling) moves to src/scripts/app.ts. Go now serves web/dist via http.FileServer for /static/* and /_astro/* with / -> web/dist/index.html, replacing the old static/index.html serving. The Dockerfile gains a node build stage (npm ci && npm run build) whose web/dist is copied into the runtime image. The three PNG assets are relocated to web/public/static so their /static/* URLs are preserved. Co-authored-by: Cursor <cursoragent@cursor.com>
The summary endpoint now returns steam_ids_searched (backed by the new search_counts table) instead of traders_indexed. Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace fabricated Steam display names and per-user avatar gradients with the real Steam ID; use a neutral person glyph avatar everywhere - Base64-inline the CSFloat logo and Steam/CSFloat icons and delete the static PNGs - Reduce README diff against master to only the "reversals" spelling fix - Offset chart event chips and hover tooltip by the plot-area inset so overlays align to the data across all period ranges Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8a04e6b. Configure here.
|
Context for re-review: since the original round of comments, the frontend moved from a single |
web/dist is a gitignored Astro build output, so any run path that skips `npm run build` (e.g. `go run` locally or a deploy that doesn't run the Node build stage) would silently serve 404s for the dashboard. Validate that web/dist/index.html exists at startup and return a clear, actionable error instead. Addresses Bugbot "Homepage missing without web build". Co-authored-by: Cursor <cursoragent@cursor.com>
PR #69 changed /api/v1/stats/reversals/daily to return each bucket's date as an RFC3339 UTC timestamp (e.g. "2026-07-13T00:00:00Z") instead of a plain "YYYY-MM-DD" string. The chart's x-value parser previously appended "T00:00:00Z" to that string, which now produces an invalid date (NaN). Parse the ISO string directly; the trailing "Z" keeps it unambiguously UTC so day buckets never shift by a browser-local offset. Co-authored-by: Cursor <cursoragent@cursor.com>
|
hey @zedimytch, fixed the web/dist thing Bugbot flagged (server now fails fast at startup if the build output is missing, and the Docker build always produces it). also updated the chart to parse the new time.Time date format from #69, all in UTC. green now — ready for another look 🙏 |

Summary
Single self-contained `static/index.html` (inline CSS + JS, no build step) plus the icons / favicons / chart-event data it consumes.
Layout
Dependencies
Heads-up for review
Test plan
Made with Cursor
Note
Low Risk
Static HTML/JSON and CDN assets only; no server auth or data-model changes in this PR. Main follow-up is replacing placeholder Steam display names before production.
Overview
Replaces the minimal landing search page with a self-contained public dashboard in
static/index.html(inline CSS/JS, no build). The page now loads KPI stats, a uPlot reversal volume chart with a 7d–1y period picker, a recent reversals table with client-side “load more,” and an upgraded Steam ID lookup that shows a compact result chip (verdict, placeholder avatar/name, Steam/CSFloat links). CS2 timeline annotations come from newstatic/cs2-events.json, rendered as stacked chips on the chart for the selected window.README.mdfixes a typo and documents that the dashboard is served at/fromstatic/index.html. Trader display names are deterministic placeholders derived from Steam ID until real profile data exists; chart/table/search call the existing public/api/v1/...endpoints and Material Symbols + uPlot via CDN.Reviewed by Cursor Bugbot for commit 0865861. Bugbot is set up for automated code reviews on this repo. Configure here.