← PROTOTYPES
SCINTILLA · M32 · HUB POLISH 23 SEP 2026 · hub/polish-20260923 · base b6913cb · nothing deployed

Two things Alan asked for on the evening of the 23rd: a number that changes should scintillate — in the tape, on the board, in the cohorts — and the company read should stop passing off months-old words as today's. The first is built and measured. The second turned out to have a specific, provable cause, and the Hub now states the age of every section on its face.

PART ONE · SCINTILLATION

The rule, enforced in one primitive rather than at each call site: a value glows only when what the reader can see actually changed, in the direction it moved, for 520 ms, in colour and text-shadow alone — never a size, a weight, a background or a padding, so a glow can never shift the row under the eye. prefers-reduced-motion switches it off entirely; the number still updates, it just sits down.

THE GLOW, ON THE LIVE BOARD, ACROSS ITS OWN 520 MS

Captured on the real page with production data, driven through the page's own provider path (patch(t, price, "PROVIDER") — the call scProviderTick makes per symbol), with the browser's animation clock slowed to 6% so a single glow could be sampled.

board rows at the moment of the change
0 ms — the moved values take the colour of their direction.
board rows 90ms into the glow
90 ms — green up, red down, with a soft halo. The percentage carries it too.
board rows 200ms into the glow
200 ms — already fading back.
board rows 330ms into the glow
330 ms — nearly returned to the row's own colouring.
board rows after the glow has finished
520 ms — finished. Nothing moved; only the colour travelled.

WHAT IT COSTS, MEASURED — 364 ROWS, EVERY TICK

Headless Chromium Chromium 151.0.7922.34, 364 rows, 20 ticks per arm, two passes each, main-thread time read from the browser's own performance counters. The before arm is the shipped code verbatim — the price flash that restarts a CSS animation with void node.offsetWidth.

ARMMAIN THREAD / TICKLAYOUTS / TICKSTYLE (20 TICKS)LAYOUT (20 TICKS)
no glow at all (floor)3.7 ms12 ms28 msthe same values written, nothing animated
shipped flash · price only170.6 ms289771 ms1115 msone forced synchronous layout per changed row
M32 · price AND percentage67.7 ms29177 ms274 msWeb Animations API, and only for rows on screen

So the new behaviour covers twice the cells for 2.5× less main-thread work than the flash it replaces, and 10× fewer layouts. Per-pass figures: before 155 and 186 ms/tick, after 67 and 68 ms/tick. At the live 2-second cadence the after arm is about 3.4% of one core.

WHERE THE SAVING COMES FROM

Two changes, both structural. Restarting a CSS animation requires reading layout, so the shipped flash forced 289 layouts a tick at 364 rows; Element.animate restarts without reading anything. And a glow nobody can see is pure cost — the ALL board holds the whole universe while the window shows about thirty rows, so each cell registers itself once with a single IntersectionObserver and a row scrolled out of view now gets its new value with no animation at all. No call site had to change for either.

WHERE IT SHOWS

THE TAPES NOW KEEP THEIR PLACE

Every 45 seconds renderTapes replaced the whole band with new HTML. That threw away the marquee's scroll position (the band jumped back to its left edge mid-scroll) and destroyed any glow in flight, so a tape could never show a change at all. A tick that changes only numbers now patches the items where they stand and glows the ones that moved; a real change of membership or order still rebuilds, because only then are new nodes needed. Every copy of a repeated segment is patched, so the second half of the loop never scrolls in stale.

PART TWO · WHY THE READ GOES STALE

Alan: "All of these sections of the read, verdict, business catalyst, are super stale as far as I've read… why do they go stale to begin with?"

Because a row was dated by the job, not by the words. The writer, read-engine, ends every run with updated_ts: now on every row it upserts, changed or not. Read live on 23 Sep: all 386 verdict rows and all 206 business/catalysts/ watch rows carried 2026-09-23T23:10Z — minutes old. The words in them had not changed since June or July. The Hub, having nothing else to date them with, showed no age at all.

EVERY STALE SECTION ON THE COMPANY VIEW · SOURCE · LAST WRITTEN · WHY

SECTIONTABLE / JOBLAST REAL WRITEWHY IT STOPPEDFIXED HERE
READ · BUSINESSread_blocks.business ← ticker_context.business_now / deep_dive
job: dossier-refresh
2026-06-15 → 2026-07-22
(63–100 days)
dossier-refresh has not run since 2026-07-22T03:43Z — its own change_log entries stop there. It is an Anthropic-API job, so a restart spends credit per ticker.Age on its face, dated from enriched_ts
READ · CATALYSTSread_blocks.catalysts ← ticker_context.catalystssamesame job. AAPL still reads "next earnings ~Jul 29 2026" — three months past.same
READ · WATCHread_blocks.watch ← ticker_context.watch_notessamesame jobsame
READ · VERDICTread_blocks.verdict, generated from composite_staged (tf=D)2026-08-24 for 364 of 386 tickers
(22 written today)
the legacy daily composite stopped advancing for the universe on 24 Aug; the narrative is re-generated from a frozen number and re-stamped.Dated by the composite it was written from
NARRATIVE BASISread_blocks.basis2026-09-23 (today)correct — it is generated live each runwording corrected to what was measured
Analyst actions (grades)analyst_grades, 34,529 rows2026-06-09 (106 days)no writer has added a row since June. Not diagnosed further in this lane — it is a different ingester.reported only
Press releasescompany_releases2026-07-02 · 5 rows in totalthe table was created and never filled — 5 rows across the whole universe.reported only
Earnings call transcriptsearnings_call_transcripts, 1,405 rows2026-09-11 (12 days)healthy for an earnings cadence—
News · quotes · profile · Geigernews, live_quotes, company_profile, provider artifact2026-09-23 (today)current—

WHAT THE COMPANY VIEW NOW SAYS

Each tab of the read carries the date of its own source: the dossier overlay for BUSINESS, CATALYSTS and WATCH; the composite the narrative was generated from for VERDICT. Under two days it is quiet, past two days it is marked, past a month it is drawn in the bear colour — and a section whose date cannot be proved says SOURCE DATE NOT RECORDED rather than looking current. The chip's tooltip names the re-stamp explicitly, so the job's own run time is visible but never mistaken for the age of the words.

the CATALYSTS tab with a 100-day-old dossier chip above text quoting a June price
CATALYSTS, AAPL, production rows. The chip reads DOSSIER · AS OF 2026-06-15 · 100D OLD — and the words under it prove it: "next earnings ~Jul 29 2026", "WWDC 2026 (Jun 8–9) is now in the rear-view", "stock ~$291" against a $337 tape. Until today the page showed no age at all.
the BUSINESS tab with its age chip
BUSINESS — dated from the dossier that wrote the words, not the job that re-stamped them.
the VERDICT tab on a phone
VERDICT at 390 — COMPOSITE BASIS · AS OF 2026-08-24 · 30D OLD, the frozen composite it was generated from.
CAUGHT IN THE ACT, WITHIN THE HOUR

The chip's tooltip carries the re-stamp. At 23:10Z it read 2026-09-23 23:10Z; on the next capture, under an hour later, 2026-09-24 00:00Z. The job had run again and re-dated every row. Both content dates — 2026-06-15 and 2026-08-24 — did not move at all.

HOW THESE FOUR WERE RENDERED

The panel above is drawn by the page's own render functions (readPanelHTML, readTxtHTML, readAgeHTML, readSectionAge) under the page's own stylesheet, fed AAPL's production rows read live from read_blocks, ticker_context and composite_staged. The live company view could not be driven headlessly for these shots — with its other hosts blocked, the YouTube and X panels retry and the page never goes idle for a screenshot — so the board captures above are from the live page and these four are this render. The values in them are production's, unedited.

THE UPSTREAM FIX, WRITTEN AND NOT DEPLOYED

candidate/read-engine-content-date.patch stops the re-stamp: a row whose body is byte-identical to the stored one is not written at all, so updated_ts keeps the date the words last changed. It is a deployed edge function in another repository — this lane does not deploy, schedule or invoke it, and the Hub deliberately does not trust read_blocks.updated_ts until it is in.

WHAT THIS DOES NOT FIX

The words are still from June and July. Only dossier-refresh writes them, it last ran on 22 July, and restarting it spends Anthropic API credit per ticker — a coordinator decision, not one this lane may take. What changed today is that a June paragraph can no longer pass for today's.

THE PAGE AT BOTH SIZES

the Hub board at 1680 with prices mid-glow
1680 — the board mid-tick, 18 rows moved through the provider path.
the Hub at 390
390 — the same build on a phone.

HOW THIS WAS PROVEN