Skip to content

static: public Reverse Watch dashboard#71

Open
ZukwiZ wants to merge 5 commits into
masterfrom
feat/public-dashboard-ui
Open

static: public Reverse Watch dashboard#71
ZukwiZ wants to merge 5 commits into
masterfrom
feat/public-dashboard-ui

Conversation

@ZukwiZ

@ZukwiZ ZukwiZ commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Single self-contained `static/index.html` (inline CSS + JS, 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
  • 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, no build step

Heads-up for review

  • Steam display names are dev placeholders. The current implementation deterministically hashes a Steam ID into a placeholder string so the search-result chip has something to show against locally seeded synthetic data. For production we need a real source — three options worth a quick decision before this merges:
    1. Extension-side enrichment (the extension already has Steam Web API access).
    2. Server-side batched Steam Web API call with short cache.
    3. DB-cached column populated lazily on first lookup.
  • Depends on Add Models #1 and Core Infrastructure #2 for the dashboard to actually function (in-memory static serving + the three new public endpoints). Reviewable in isolation against master — the HTML/JS is self-contained — but won't render real data until those land.

Test plan

  • After merging Add Models #1 + Core Infrastructure #2 + fix cursor proto field usage #3 (or locally on a branch that has all four): run `go run ./cmd/seed`, then load `http://localhost:80/\`
  • Click through every period in the picker (7d / 30d / 3m / 6m / 1y) — chart re-renders with the right number of buckets
  • CS2 event annotation chips appear at expected dates (see `static/cs2-events.json`)
  • Submit a known flagged Steam ID — result chip shows avatar + display name + working Steam + CSFloat profile links
  • Submit a clean Steam ID — result chip shows the clean state
  • DevTools mobile viewport (e.g. iPhone 14) — single-column layout, KPI cards stack, chart is readable, table paging still works
  • `curl -I http://localhost:80/\` returns the right `Content-Length` (depends on Add Models #1)

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 new static/cs2-events.json, rendered as stacked chips on the chart for the selected window.

README.md fixes a typo and documents that the dashboard is served at / from static/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.

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>
Comment thread static/index.html Outdated
Comment thread static/index.html Outdated
@GODrums

GODrums commented May 29, 2026

Copy link
Copy Markdown

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.

@zedimytch zedimytch self-requested a review June 3, 2026 16:54
Comment thread web/public/static/csfloat-icon.png Outdated
Comment thread web/public/static/csfloat-logo.png Outdated
Comment thread static/cs2-events.json Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We can inline this as a const in the script block in our html file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — inlined as a CS2_EVENTS const in the script block; the JSON file is removed.

Comment thread static/index.html Outdated
Comment on lines +1182 to +1198
// 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',
];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please remove all placeholder data. In general, this should not be committed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Comment thread static/index.html Outdated
Comment thread web/public/static/steam-icon.png Outdated
Comment thread README.md Outdated
# [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 `/`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please remove all changes, except for the spelling fix.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — the README diff vs master is now just the reverals to reversals spelling fix.

@zedimytch

Copy link
Copy Markdown
Collaborator

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.

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 /static/* and /_astro/* to the user.

This will require a Node build stage in the Dockerfile.

dist/
  index.html
  _astro/...          # bundled JS/CSS (and optimized images, if you used src/assets)
  static/             # public/ files copied verbatim (logos, cs2-events.json)
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:

web/
├── package.json            # declares deps: astro, uplot. Defines `npm run build`.
├── package-lock.json       # pins exact versions + hashes → supply-chain control
├── astro.config.mjs        # output: 'static' + build options (assets dir, etc.)
├── tsconfig.json           # TS config (optional, only if you use .ts/.tsx)
│
├── public/                 # copied VERBATIM to dist/, paths preserved, no hashing
│   └── static/
│       ├── csfloat-logo.png      # keeps URL /static/csfloat-logo.png
│       ├── csfloat-icon.png
│       └── steam-icon.png
│
├── src/                    # SOURCE that gets compiled/bundled (not shipped as-is)
│   ├── pages/
│   │   └── index.astro     # your <head> + hero + KPIs + chart + table + footer markup
│   ├── components/
│   │   ├── ReversalChart.astro   # the uPlot chart + period picker (client island)
│   │   ├── SearchBox.astro       # Steam ID search + result chip logic
│   │   └── RecentTable.astro     # recent reversals table + "load more"
│   ├── scripts/
│   │   └── app.ts          # the JS currently in your <script> (or split per component)
│   ├── styles/
│   │   └── global.css      # the big inline <style> block moves here, gets bundled
│   └── assets/             # OPTIONAL: images you want Astro to optimize + hash
│       └── (logos here instead of public/ IF you want compression/cache-busting)
│
└── dist/                   # BUILD OUTPUT — the only thing Go serves at runtime
    ├── index.html          # compiled page (inline styles bundled out, refs hashed)
    ├── static/             # everything from public/ copied here verbatim
    │   ├── csfloat-logo.png
    │   ├── cs2-events.json
    │   └── ...
    └── _astro/             # bundled + hashed CSS/JS (and optimized images if src/assets)
        ├── index.[hash].css
        ├── client.[hash].js     # includes your bundled uPlot — no CDN
        └── ...

- 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>
Comment thread static/index.html Outdated
Comment thread static/index.html Outdated
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>
byskov and others added 2 commits June 24, 2026 14:11
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>

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread server/server.go
r.Handle("/_astro/*", fs)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/index.html")
http.ServeFile(w, r, "web/dist/index.html")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Homepage missing without web build

High Severity

The root route now serves web/dist/index.html, but web/dist/ is a gitignored build output. This means the dashboard at / will be missing for local development and deployments that don't include the web build step.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8a04e6b. Configure here.

@ZukwiZ

ZukwiZ commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Context for re-review: since the original round of comments, the frontend moved from a single static/index.html to an Astro app under web/ (per the discussion about serving it via a build step). uPlot is now bundled from npm (no CDN), the page builds to web/dist and is served via http.FileServer, and the icons are inlined as data: URIs. A few earlier line comments pointed at static/index.html, which no longer exists — I have replied on each thread pointing to where the change landed in the new structure.

@ZukwiZ ZukwiZ requested a review from zedimytch June 24, 2026 13:40
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.

4 participants