← PROTOTYPES

SCINTILLA · DASHBOARD · 23 SEP 2026

Two things were asked for. The right half of the dashboard is now one set of four tabs instead of a band stacked on top of a strip of three. And the Hub's slow opening was measured before anything was touched — the page itself was never the slow part, and the Geiger was never the slow part either.

"Cohort compare and the map, rotation and relative should all be tabs of the same right half."
"The hub. It loaded extremely slow. The Geiger is slow."
— Alan, 23 September

1 · One strip of four tabs

COHORT COMPARE used to sit above the MAP / ROTATION / RELATIVE tabs as a permanent band, taking about 110 pixels out of a pane only about 330 pixels tall. It is now the first of four tabs, and whichever tab you choose owns the full height of the right half.

The cohort compare tab at 1680 wide
COHORT COMPARE, 1680 wide. The strip reads COHORT COMPARE · MAP · ROTATION · RELATIVE, with the first one lit. The fan fills the pane: five cohorts above the zero line (CRYPTO +0.60, AI HW +0.43, MEGA +0.26, INDEX +0.21, AI SW +0.19) and seven below it (METALS −0.06 down to THEME −0.26), each column carrying its word — constructive, mixed, broken — and its trend/momentum pair. The board on the left is untouched and priced.
The map tab at 1680 wide
MAP, 1680 wide. Every control it had is still there — the shared timeframe, the five modes (plus TREEMAP), GRAIN / FILL · OPACITY / LEVEL — and the heat grid now runs the full height of the pane instead of stopping where the cohort band used to start.
The rotation tab at 1680 wide
ROTATION, 1680 wide. The RRG with its four quadrants, eleven sector tails against SPY, the transport and the honest "LIVE · SEP 23 12:00 PM" stamp.
The relative tab at 1680 wide
RELATIVE, 1680 wide. Eleven sectors rebased to zero at the left edge, with the window chips (1M · 3M · 6M) and each line labelled at its right end: XLE +7.7 at the top, XLU −9.1 at the bottom.
The cohort tab on a phone
COHORT, 390. All four tabs fit one row because the first one shortens itself to COHORT. The fan keeps the two-row shape it has had on phones since 18 September — six cohorts, the rest one scroll down.
The map tab on a phone
MAP, 390. Controls wrap onto two lines; the tiles are four across.
The rotation tab on a phone
ROTATION, 390. The RRG keeps its square shape on a phone.
The relative tab on a phone
RELATIVE, 390. The same eleven lines, labels at the right edge.

2 · The speed, measured before it was touched

Every number below is a real browser at 1680×1050 with an empty cache, loading the page from scratch. The "before" runs are the live site at scintillahub.ai and an unchanged copy of production on this machine; the "after" runs are the same copy with the changes applied, loaded alternately with the unchanged one so that a slow minute on the data service hits both equally.

The live site before anything changed — 3 cold loads of scintillahub.ai

WhatMedianThe three runs
First paint — the page appears172 ms180 ms, 172 ms, 144 ms
Tapes moving230 ms250 ms, 230 ms, 221 ms
Board rows carrying prices17.5 s30.2 s, 17.5 s, 16.9 s
Geiger values on the rows17.5 s30.2 s, 17.5 s, 16.9 s
Requests · weight104 · 478 KB104, 131, 87

So the page itself was never slow: it painted in about a fifth of a second and the tapes moved a fifth of a second after that. What was slow was the board's prices — and the Geiger column filled at exactly the same moment as the prices in every single run, because both are painted from the same board rows. The Geiger was not slow. It was waiting, like everything else, for one request.

After the changes — the unchanged copy and the changed copy, loaded alternately

Loaded one after the other, 3 times each, on the same machine and the same minute, so a slow spell on the data service lands on both. This evening the data service was far slower than it was at the first measurement, which is why the unchanged copy looks worse here than it did live earlier — that is the honest comparison to read: the two columns, not this table against the one above.

WhatUnchanged (production)With the changes
Board rows carrying prices — median9.6 s3.7 s
First paint — median64 ms68 ms
Tapes moving — median147 ms140 ms
Whole-universe price requests per load — median53
Duplicate requests suppressed — median05
Total requests — median8974

Every run, in full: unchanged 5.1 s / changed 3.7 s; unchanged 9.7 s / changed 5.0 s; unchanged 9.6 s / changed 3.6 s.

3 · The three causes, with numbers

Cause 1 — the same request, three or four times at once

The whole-universe price request (all 364 tickers in one call) was being made by three different parts of the page at the same moment, none of them aware of the others: the board's two-second tick, the board's own price read, and the fear & greed reading. Timed from this machine with plain requests, one after another:

What was askedHow long it took
364 tickers, on its own3.6 s
364 tickers, four at once — what the Hub was doing7.7 s · 8.9 s · 9.8 s · 10.3 s
Six batches of 61, one after another — the obvious "fix" that is not one41.9 s in total

The requests fight each other, so asking four times costs far more than four times nothing — it costs everyone. Splitting the request into small batches is worse still, which is why that was not done.

Fixed: while a request to that address is in flight, everyone who asks for the same thing is handed the same one. The symbol lists are now written in the same order by every caller so they can be recognised as the same request. In the runs above this suppressed five duplicate requests per load.

Cause 2 — the fear & greed reading ran three times over

That reading caches itself for ten minutes, but it only wrote the cache when it finished. Anyone who asked while the first one was still working started another. Three were running at once on a cold load, and each one pulls the whole universe of prices plus five sets of daily bars — which is where the duplicate /candles (thirteen in one load) and the duplicated database reads came from as well.

Fixed: callers now share the run that is already going. And because that reading is not what you look at first, it now waits for the board's first accepted prices before it starts — with a ten-second cap, so a dead data service can never hold it up for ever.

Cause 3 — the board waited for all 364 before showing any

You can see about sixty rows. The board was holding all of them back until prices for all 364 arrived, including every ticker in a cohort you were not looking at. Sixty tickers came back in about a second when the service was healthy; 364 took several seconds at best and, this evening, sometimes did not come back at all.

Fixed: the first request asks only for the rows on screen, and the very next one — a quarter of a second later — asks for the whole universe. So the visible prices arrive early and full coverage still arrives within a second, which means switching cohorts still cannot land you on an unpriced row.

The suspect that was not guilty: the "how unusual" watcher

The brief asked whether the watcher that re-runs on every change to the page was a cause. It was measured rather than assumed: every watcher on the page was counted and timed for a whole load. The one in question ran 18 to 23 times, for one millisecond in total, across roughly 450 page changes. It is not a cause and it was left alone.

Long freezes of the main thread were also counted: none at all in the healthy runs. The page was never the slow part.

4 · What is left for the data service, not the Hub

These are for whoever owns the chart API. Nothing here was changed.

WhatMeasuredWhen
Whole-universe prices, one request alone3.6 s19:35Z
The same request during a bad spell15.0 s, and a 59-ticker request returned an error after 20.1 s20:01Z
The same requests minutes later59 tickers 0.31 s · 364 tickers 1.2-2.2 s · the Geiger artifact 1.8 s20:02Z
What that does to the HubDuring the bad spell neither the unchanged Hub nor the changed one could show a single price within 90 seconds. That is not something the Hub can fix.

Two things are worth someone's attention there: the first request for a set of tickers is dramatically slower than the same request a moment later, which is what makes duplicate requests so expensive; and the service was returning 503 for a small request at 20:01Z while answering a larger one.

5 · How often the Hub asks, once it has loaded

Watched on the live site for 105 seconds, read-only: the board's price loop is healthy — 34 runs, no failures, a fresh request every two to three seconds, each one for the whole 364-ticker universe. The Geiger artifact is a separate read, held for 30 seconds and refreshed with the board's own 45-second rebuild; on a cold load it was fetched three times within two seconds (that duplication is one of the things now shared).

So the Geiger is not on a slow cadence and is not stuck. Its numbers appear when the board rows appear, which is why fixing the prices fixed the Geiger's arrival too — 28.2 s to 22.1 s during the bad spell, and with the board's prices at 3.7 s when the service was healthy.

Worth someone's judgement, not changed here: asking for all 364 tickers every two seconds is a lot of asking. It is also what keeps that endpoint warm, and warm is 0.31 s where cold is seconds — so it should not be changed without measuring what happens when it stops.

6 · Where each number on this page comes from

7 · What could be wrong

8 · What I did not do