> For the complete documentation index, see [llms.txt](https://docs.trover.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trover.tech/engineering/verify-2026-09-04-nft-trending-board.md).

# Verify — NFT trending board (AlphaSharks replacement, part 1)

## There IS a migration this round

`packages/db/migrations/20260903120000_nft_event_window_index` adds one index:

```sql
CREATE INDEX IF NOT EXISTS "nft_market_events_eventType_occurredAt_idx"
  ON "nft_market_events" ("eventType", "occurredAt");
```

**Why it is required, not optional.** The trending board aggregates the event tape across every collection on a chain for a time window. Every existing index on `nft_market_events` leads with `collectionId`, so none of them can serve a query whose only selective predicate is a time range — without this index the route degrades to a sequential scan that grows with the table, on a route that polls every 5s.

**It takes a write lock on `nft_market_events` while it builds.** The table is large. `CREATE INDEX` (not `CONCURRENTLY`, because Prisma runs migrations inside a transaction) blocks writes for the duration, which means the chain scanners will back up while it runs. Run it when a short ingest pause is acceptable; the scanners recover on their own afterwards, since both lanes are cursor-driven.

## Deploy

Build **before** migrate — `docker compose run --rm migrate` runs the migrations baked into its image, not the working tree, and will otherwise report "No pending migrations to apply" while this one is simply absent.

```bash
cd ~/trover
git pull --ff-only origin main

# 1. Build first, so the migrate image actually contains the new migration.
docker compose -f docker-compose.yml -f docker-compose.production.yml build api worker migrate

# 2. Apply the index (brief write lock on nft_market_events).
docker compose -f docker-compose.yml -f docker-compose.production.yml run --rm migrate

# 3. Recreate the services that changed.
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --force-recreate api worker
```

The web app ships via Vercel on push to main; nothing to do there.

## Confirm the migration actually landed

```bash
docker compose -f docker-compose.yml -f docker-compose.production.yml exec -T postgres \
  sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\d nft_market_events"' | grep eventType
```

Expect a line naming `nft_market_events_eventType_occurredAt_idx`. If it is absent, the migrate image was stale — rebuild and re-run step 2.

## Confirm the route

```bash
curl -s 'https://api.trover.tech/v1/nft-market/trending?chain=robinhood&window=15m&limit=5' \
  | python3 -m json.tool | head -40
```

Expect `window`, `windowSeconds`, `rows[]` and `liveSales[]`. Each row carries `sales`, `previousSales`, `salesTrend`, `volumeNative`, `spark[]` (24 buckets), plus `ownersPct` / `listedPct` / `floorNative`.

Check the index is being used rather than just present:

```bash
docker compose -f docker-compose.yml -f docker-compose.production.yml exec -T postgres \
  sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "EXPLAIN ANALYZE SELECT count(*) FROM nft_market_events WHERE \"eventType\" = '"'"'sale'"'"' AND \"occurredAt\" >= now() - interval '"'"'15 minutes'"'"';"'
```

Expect an `Index Scan` or `Bitmap Index Scan` naming the new index, not a `Seq Scan`.

## What to look at in the UI

New page at `/nfts/trending`.

1. The window selector (1m … 24h) changes every column, not just the ordering — sales, volume, the sparkline and the arrow are all window-scoped.
2. The arrow compares the chosen window against the equally-sized window immediately before it, so on a quiet 1m it will legitimately flip often.
3. A row click opens that collection's terminal at `/dex/nfts/<chain>/<slug>`.
4. Live Sales on the right marks anything under a minute old as "just now" in red, matching the reference.

## Known gaps (deliberate, not defects)

* **`ownersPct` and `listedPct` read from the collection row**, which is refreshed on its own cadence rather than per-window. They are current values, not windowed ones — the reference behaves the same way.
* **No NBCP column.** AlphaSharks shows it on the expanded panel; we have no equivalent computation yet.
* **The expanded side panel is not built.** Per your instruction, a row opens the collection's DEX page instead of expanding in place, so Notable Holders / Sell Wall / Momentum from the second screenshot are out of scope this round.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trover.tech/engineering/verify-2026-09-04-nft-trending-board.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
