Skip to content

One surface per crypto pair, with fuller pricing - #16

Merged
ralyodio merged 1 commit into
mainfrom
crypto-one-surface
Aug 7, 2026
Merged

ralyodio merged 1 commit into
mainfrom
crypto-one-surface

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The URL you sent — /?pair=SOL-USD#crypto — is the old in-app modal, not the page. I rendered it against prod to check: it does have pricing (bid $72.44, ask $72.64, mid, 22.1 bps spread, day high/low, previous close, venue volume — no empty fields). What it lacks is the Analysis.

Two views of the same pair had drifted apart:

Market Technical Order book Analysis
modal (/?pair=)
page (/crypto/:pair)

Rather than patch the modal — the thing you already told me to stop using — this collapses them into one surface.

Changes

  • /?pair=X → 301 → /crypto/X. The crypto modal is deleted (~5.9k of JS gone).
  • The order book moves onto the page, the one thing the modal had that the page did not.
  • New Performance section — the part that most deserves the name "pricing info" for a 24/7 asset: what it has been doing, not just what it costs right now.

Live for SOL/USD:

24h 7d 30d 90d 1y
-1.97% -2.64% -9.98% -21.21% -56.89%

plus the 52-week high/low with the dates they occurred, and session volume in the quote currency ($22,059.29). All computed from the daily bars already fetched for the indicators — no extra upstream call, no second vendor.

Where it refuses to answer

  • A period with less history than it needs shows and says why. Measuring 1y from the oldest of four months of bars would be a claim about time we cannot see.
  • Seeking is by date, not by index, so a gap in the feed cannot silently shift the window (there is a test for exactly this).
  • Market cap, circulating supply and all-time high are absent. Alpaca does not carry them, and deriving them would mean inventing a supply figure or mixing in a second vendor with different provenance. The page names the gap instead of leaving it silent — tell me if you want those, and I will wire a second source deliberately rather than fudge it.

Verification

  • 453 tests pass (12 new), tsc --noEmit clean
  • Verified live: the redirect (/?pair=SOL-USD → 301), an unknown pair falling through to the app rather than 301-ing into a 404, and every new section rendering with real figures

The redirect lives in src/crypto/routes.ts rather than inline in server.ts so it is testable and the pair grammar stays in one place.

🤖 Generated with Claude Code

The reported symptom was that /?pair=SOL-USD lacks pricing. It does have
pricing — bid, ask, mid, spread, day high/low, previous close, all populated.
What that URL lacks is the analysis, because it is the old in-app modal, not
the page. Two views of the same pair had drifted apart:

  modal:  Market, Technical, Order book        (no analysis)
  page:   Market, Technical, Analysis          (no order book)

So this collapses them instead of patching the modal. /?pair=X now permanently
redirects to /crypto/X, the crypto modal is deleted, and the page gains the one
section it was missing.

The page also gains what neither had, which is the part that most deserves the
name "pricing info" on a 24/7 asset — what it has been doing, not just what it
costs right now:

  - change over 24h / 7d / 30d / 90d / 1y, each against a dated close
  - 52-week high and low, with the dates they occurred
  - session volume in the quote currency

Computed from the daily bars already fetched for the indicators, so no extra
upstream call and no second vendor. A period with less history than it needs
reports "—" and says why: measuring 1y from the oldest of four months of bars
would be a claim about time we cannot see. Market capitalisation, circulating
supply and all-time high are absent for the same reason — Alpaca does not carry
them, and deriving them means inventing a supply figure. The page names those
gaps rather than leaving them silent.

The deep-link redirect lives in src/crypto/routes.ts rather than inline in the
server so it is testable and the pair grammar stays in one place; it accepts
every spelling the other routes do, and falls through rather than 301-ing an
unknown pair into a 404.

453 tests pass (12 new), tsc clean, verified live: the redirect, the new
sections, and every period figure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

55 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 50

Severity Rule Location
HIGH js-unescaped-html-sink public/auth.js:189
HIGH js-ssrf-outbound-request src/crypto/client.ts:109
HIGH js-ssrf-outbound-request src/providers/alpaca.ts:76
HIGH secret-generic-api-key test/auth.test.ts:190
HIGH secret-generic-credential test/credits.test.ts:24
MEDIUM js-unescaped-html-sink public/app.js:41
MEDIUM js-unescaped-html-sink public/app.js:97
MEDIUM js-unescaped-html-sink public/app.js:232
MEDIUM js-unescaped-html-sink public/app.js:244
MEDIUM js-unescaped-html-sink public/app.js:246
MEDIUM js-unescaped-html-sink public/app.js:338
MEDIUM js-unescaped-html-sink public/app.js:354
MEDIUM js-unescaped-html-sink public/app.js:358
MEDIUM js-unescaped-html-sink public/app.js:362
MEDIUM js-unescaped-html-sink public/app.js:381
MEDIUM js-unescaped-html-sink public/app.js:385
MEDIUM js-unescaped-html-sink public/app.js:389
MEDIUM js-unescaped-html-sink public/app.js:572
MEDIUM js-unescaped-html-sink public/app.js:657
MEDIUM js-unescaped-html-sink public/app.js:678
MEDIUM js-unescaped-html-sink public/app.js:800
MEDIUM js-unescaped-html-sink public/app.js:801
MEDIUM js-unescaped-html-sink public/app.js:810
MEDIUM js-unescaped-html-sink public/app.js:848
MEDIUM js-unescaped-html-sink public/app.js:908
MEDIUM js-unescaped-html-sink public/app.js:913
MEDIUM js-unescaped-html-sink public/app.js:926
MEDIUM js-unescaped-html-sink public/app.js:977
MEDIUM js-unescaped-html-sink public/app.js:1056
MEDIUM js-unescaped-html-sink public/app.js:1062
MEDIUM js-unescaped-html-sink public/app.js:1129
MEDIUM js-unescaped-html-sink public/app.js:1169
MEDIUM js-unescaped-html-sink public/app.js:1218
MEDIUM js-unescaped-html-sink public/app.js:1234
MEDIUM js-unescaped-html-sink public/app.js:1377
MEDIUM js-unescaped-html-sink public/app.js:1379
MEDIUM js-unescaped-html-sink public/app.js:1396
MEDIUM js-unescaped-html-sink public/app.js:1546
MEDIUM js-unescaped-html-sink public/app.js:1550
MEDIUM js-unescaped-html-sink public/app.js:1560
MEDIUM js-unescaped-html-sink public/auth.js:41
MEDIUM js-unescaped-html-sink public/auth.js:59
MEDIUM js-unescaped-html-sink public/auth.js:67
MEDIUM js-unescaped-html-sink public/auth.js:106
MEDIUM js-unescaped-html-sink public/auth.js:193
MEDIUM js-unescaped-html-sink public/auth.js:256
MEDIUM js-unescaped-html-sink public/auth.js:258
MEDIUM js-unescaped-html-sink public/auth.js:275
MEDIUM sql-template-interpolation src/cli.ts:552
MEDIUM sql-template-interpolation src/cli.ts:1033

…and 5 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit c297598 into main Aug 7, 2026
4 checks passed
@ralyodio
ralyodio deleted the crypto-one-surface branch August 8, 2026 00:37
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.

1 participant