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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0865861. Configure here.
| width: 2, | ||
| fill: (u) => { | ||
| const ctx = u.ctx; | ||
| const grad = ctx.createLinearGradient(0, 0, 0, u.bbox.height); |
There was a problem hiding this comment.
Chart fill gradient misaligned from plot area
Low Severity
The area-fill gradient is created with createLinearGradient(0, 0, 0, u.bbox.height), but u.bbox describes the plot area within the canvas — it starts at u.bbox.top, not at y=0. The correct coordinates are (0, u.bbox.top, 0, u.bbox.top + u.bbox.height). Without this, the gradient is offset from the actual fill region by u.bbox.top pixels, causing a slight opacity mismatch at the top and bottom of the filled area.
Reviewed by Cursor Bugbot for commit 0865861. Configure here.
| els.chipAvatar.textContent = name.charAt(0).toUpperCase(); | ||
| els.chipAvatar.style.background = avatarGradient(steamId); | ||
| els.chipSteamLink.href = 'https://steamcommunity.com/profiles/' + encodeURIComponent(steamId); | ||
| els.chipCsfloatLink.href = 'https://csfloat.com/stall/' + encodeURIComponent(steamId); |
There was a problem hiding this comment.
Steam ID precision loss corrupts profile links
Medium Severity
SteamID is uint64 in Go (domain/models/steamid.go). Typical Steam IDs like 76561198012345678 exceed JavaScript's Number.MAX_SAFE_INTEGER (≈9×10¹⁵). When data.steam_id is parsed from JSON as a number, the last digits can be silently rounded, causing chipSteamLink and chipCsfloatLink to navigate to the wrong Steam/CSFloat profile. The same issue affects row.steam_id in the recent-reversals table.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 0865861. Configure here.
|
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.
Do we have this image as an svg anywhere? We'd be able to inline it in our html file instead of needing to serve it.
We could also base64 encode the image.
There was a problem hiding this comment.
Do we have this image as an svg anywhere? We'd be able to inline it in our html file instead of needing to serve it.
We could also base64 encode the image.
There was a problem hiding this comment.
We can inline this as a const in the script block in our html file.
| // 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.
| <p>Powered by <a href="https://csfloat.com" target="_blank">CSFloat</a></p> | ||
| </footer> | ||
|
|
||
| <script src="https://cdn.jsdelivr.net/npm/uplot@1.6.32/dist/uPlot.iife.min.js"></script> |
There was a problem hiding this comment.
We should self host the uPlot.js and uPlot.css files.
There was a problem hiding this comment.
Do we have this image as an svg anywhere? We'd be able to inline it in our html file instead of needing to serve it.
We could also base64 encode the image.
| # [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.


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.