Skip to content

Visual + data-encoding overhaul (Lakes 0-8)#4

Draft
aurph wants to merge 26 commits into
mainfrom
worktree-visual-overhaul
Draft

Visual + data-encoding overhaul (Lakes 0-8)#4
aurph wants to merge 26 commits into
mainfrom
worktree-visual-overhaul

Conversation

@aurph

@aurph aurph commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Complete visual and data-encoding overhaul across all tools, built lake by lake with owner checkpoints.

  • Lake 1 - Design tokens: surface/ink scales, semantic + CVD-validated 10-slot categorical + data-quality colors, type scale with tabular figures, shared chart theme; ~800 hardcoded hexes, 350 px font sizes, 231 inline styles migrated. Fixed Tailwind 3 silently dropping slash-opacity on var() colors (color-mix wrapper).
  • Lake 2 - Neocloud chart rebuilt on visx: true time axis, log 1-2-5 ticks, anchor-vs-interpolation honesty (dashed synthetic spans, no splines), collision-avoided direct labels, hover isolation, URL-shareable chip filters, unified crosshair, range bands, grid default view (owner pick).
  • Lake 3 - The Stack: honest sparklines (window domain + prior-close reference), cards/table/heatmap views persisted per user, 6.9x faster mount (1.27s -> 0.18s measured), defined states for every edge case, approved mkt-cap sort fix.
  • Lake 4 - Overview: compact data-first header (honest NPI change labeling), catalyst triple-encoding deduped (owner pick), semantic sector chip grid, NPI chart on a true time axis with ranges, baseline-anchored gauge sparklines.
  • Lake 5 - Power Map: size=MW/color=status encoding with legend, clustering + spiderfy, one reconciled color system (was three contradicting), linked operator-table -> map filtering, dead filter options removed, hover perf fix.
  • Lake 6 - one commit per remaining tool: Queue est. flags, TheTrade ramp fixes + invalid-mix state, Catalyst error card, SupplyChain rAF drag + keyboard nodes, Portfolio sort/copy/links, gpu-economics true cheapest badge, compute-frontier timeline sort.
  • Lake 7 - trust layer: AsOf freshness chips with stale badges, in-place ErrorState with retry everywhere, sr-only chart tables, focus-visible, reduced-motion, ticker pause control, AA contrast bumps, per-tool responsive policy (Power Map honest desktop-only card).
  • Lake 8 - tests: 100% line/function coverage on both chart transform modules, state-primitive snapshots, tokens.ts<->index.css sync test; suite 160 tests.

Screenshots and the Lake 0 audit were delivered at checkpoints during the build.

🤖 Generated with Claude Code

https://claude.ai/code/session_011dDxxKQpH3o4CA7qnDFGdN

aurph added 26 commits July 2, 2026 14:08
…quality colors, type scale, chart theme

Categorical palette CVD-validated (min adjacent dE 13.9 both surfaces).
Tabular figures on all mono/table numerals.
…ier to tokens

Stack layers: 10 thesis layers own the 10 categorical slots, periphery
layers (mining, crypto operators, benchmarks) take neutral ink tiers.
Catalyst categories and supply-chain stages on distinct series slots
instead of four near-identical oranges.
Migrate every remaining page and component (PowerMap, TiltOverview,
neocloud-intel, TheTrade, Queue, power-deals, gpu-economics, Portfolio
Overlay, stock/sector/region/operator pages, blog, admin, EmailCapture,
NewsTicker, sidebar, App, not-found) off hardcoded hex/px/font values
onto the token layer.

Fix: Tailwind 3 silently drops slash-opacity on plain var() colors -
every bg-brand/10 style tint compiled to no CSS. Token colors now wrap
in color-mix with <alpha-value> so tints work; verified by compiling
probes through the real config.

Remaining literals are documented exceptions: external company brand
colors (PowerMap operator map), data JSON.

Categorical unifications: nuclear=violet everywhere (was orange/purple/
amber by page), vendor pair NVIDIA=brand/AMD=cyan, status colors from
STATUS_COLORS, semantic green/red only for gain-loss state.
True UTC time scale (pixels proportional to elapsed time), log y with
1-2-5 ticks and a corner 'log scale' label, month/quarter boundary
ticks that densify as the range shortens, 3M/6M/1Y/ALL selector.

Data honesty: sourced month anchors = solid dots, recorded days =
small dots, spans between points render dashed at reduced opacity
when interpolated (linear only, no splines), launch ring on each
series' first point, 'daily recorder starts <date>' note while the
recorder is empty, interp/est flags in the tooltip.

Legibility at 10 series: collision-avoided right-edge direct labels,
hover isolates a series (others drop to 22%), chip filters (click =
solo, shift-click = toggle, All resets) persisted in URL params so
views are shareable, unified crosshair tooltip sorted by price.

Range bands: current observed low-high marketplace range behind each
line when <= 3 series visible, explained in the footnote.

Overlay / grid view toggle: grid = small multiples, shared time
domain, independent log y per panel.

Table: honest sparkline column (same anchor/fill treatment, per-series
domain), row hover highlights the series in the chart, replaced the
memory column (still on cards + row tooltip); fixes the 13-spans-in-
12-columns row bug. Removed the hasRealSeries gate - the history chart
now always renders, honestly.

New pure transform module client/src/lib/gpu-series.ts (anchor/fill
classification, clipping, log ticks, label solver, sparkline domains)
sized for Lake 8 unit coverage. Adds @visx scale/shape/group/axis/event.
…ation, edge cases

Sparklines (raw SVG, no per-card Recharts): y-domain is the window's own
[low, high] with 10% padding, color = the window's net direction (was
today's quote change painted onto any window), faint dashed reference
line at prior close (1D) / window open (5D, 1M) so shape reads as
above/below the baseline, defined 'no intraday data' empty state.

Views (persisted per user in localStorage):
- cards: existing layout on the new sparklines
- table: dense rows, all 13 layers as collapsible group headers,
  sortable ticker/price/1D/5D/1M/mktcap/PE/revGrowth with nulls always
  last; 5D and 1M columns compute from each window's own price series,
  fetched lazily via the same endpoint
- heatmap: squarified treemap, tile = market cap, color = selected
  window's change on a diverging semantic ramp saturating at +-4%,
  grouped by layer, ETFs excluded (AUM is not corporate market cap),
  unsized tickers listed honestly, stale tiles gray with dashed border

Perf (measured via React Profiler + CDP, 3 runs each, warm cache):
before 1272/1251/1247 ms mount render with 97 Recharts instances;
after 181/185/181 ms with 1 (correlation scatter) - 6.9x faster.
Off-screen layer sections skip render via content-visibility: auto.

Edge cases: P/E null and negative are explicit states (INTC shows an
em dash with an explanation, not a blank), missing rev growth and
market cap render dashes, stale rows show delayed badge + all-dash
changes, market state hoists to one page chip ('market closed') with
per-row badges only when a ticker differs from the majority.

Fixes the approved parseM bug (M sorted equal to $50B); market cap
now prefers the live numeric field (static display strings were months
stale: cards said $3.8T for AAPL while live is $4.5T).

Server: additive passthrough of marketCap/marketState/previousClose
from the existing quote call - no new fetches, no route changes.
…me axis + ranges, honest gauge sparklines

A: hero replaced by a data strip - page title, live NPI with change
labeled by its TRUE span (the recorder has a 22-day gap; calling that
move 1D would lie), last-updated, live dot. Data starts above the fold.

C: sector chips are an aligned grid with uniform anatomy, colored
purely semantically - the same diverging magnitude ramp as the Stack
heatmap. Sector identity lives in the label, not a hue.

D: NPI gauge chart on a true time axis (recorder gaps now render as
gaps in time, not one category step), clean month-boundary ticks,
3M/6M/1Y/ALL range selector matching Neocloud, linear interpolation
instead of monotone splines, baseline stated in the subtitle instead
of a clipped in-chart label. Sentiment sparklines get honest domains:
window values UNION the formula baseline (AI 72 / stress 68, mirroring
server/indices.ts) with a dashed baseline reference - a one-point
wiggle can no longer fill the full height.

E: mover tags already on stable categorical tokens; filled the five
missing sector short-labels (renewableGeneration rendered raw).

Also: Grid Stress KpiCard red variant now renders red (was orange),
demand chart no longer double-encodes 2024 as actual and projection,
merged the duplicate /api/kpis query.
…ner pick)

Same five catalysts were rendered three times in one column: calendar
dots, Next 5 Upcoming inside the calendar card, and a standalone
Next 5 Catalysts card. The standalone card is deleted; calendar dot
days gain a hover popover listing that day's events (dots are now
functional, not decorative); the single Next-5 list stays. Click-to-
pin day detail unchanged.
Markers: size = MW (sqrt scale) with an in-map legend showing reference
sizes, color = facility status from STATUS_COLORS. The dashed rotating
ring encoded construction status - redundant with the status color, so
it is deleted. Overlapping Southeast markers cluster with count badges
(leaflet.markercluster) and spiderfy/zoom on click.

Color reconciliation: the page had three disagreeing systems (legend vs
stressColorForRTO vs RTO_STRESS_COLORS). Now one truth: status ->
STATUS_COLORS, RTO stress -> one SEMANTIC ramp used identically by
region fills, legend, and table badges.

Impossible filter options removed (dataset floor is 400 MW; '<100 MW'
could never match) - buckets are now 400-600 / 600-1000 / 1GW+.

'>=400 MW ONLY' banner collapsed to an info popover; GW/TWh strip kept.
Upcoming projects: scroll-snap cards, hidden scrollbar, edge fade,
'show all' expansion.

Operator table: inline reserve-margin bar next to the number, stress
badges from the same semantic ramp, duplicate aiSignal column dropped,
row click filters the map to that RTO's facilities (linked views) with
clear chips and active-row highlight.

Perf: hover no longer rebuilds every marker layer (hoveredId out of
the effect deps, imperative restyle - DOM nodes verified stable).
Pulse animation honors prefers-reduced-motion; rotate keyframe gone.
The old-shape fallback injects literals (Dominion 47.1 GW, 77%
withdrawal, Meta Hyperion, Stargate) that rendered indistinguishable
from sourced data. They now carry est. flags whenever the fallback
produced them (verified by intercepting the API with the old shape).
Rows without notes/sources no longer render a cursor-help trigger that
opens an empty tooltip.
…amp, invalid-mix state

Approved calc fix: buildoutChart dropped each preset's last ramp entry
(ramp[i-1] indexing); bars now sum exactly to the preset total
(pixel-verified). Custom mode replaces flat GW/5 bars with a labeled
assumed S-curve ramp scaled to the input total, so the timeline has
time-shape without pretending precision. A mix that does not sum to
100% now shows 'Mix sums to N% - adjust to 100%' in place of the chart
and blanks mix-dependent outputs instead of charting garbage silently.
Tooltip/cursor/axis fully on chart theme.
…, flat dots

'Upcoming Earnings' renamed to Upcoming Catalysts (the list includes
non-earnings items). Fetch failure now renders an in-place error card
instead of a silently blank calendar, and header counts hide instead
of showing misleading zeros. Timeline dot rings were a cosmetic halo
with no data meaning - flattened.
…, keyboard nodes

Dragging re-rendered all 21 nodes + 44 links per pointer event; now at
most one render per animation frame (rAF-throttled, positions still
tracked per event). First render shows a full-height skeleton instead
of a null flash. Entrance animations wrapped in
prefers-reduced-motion: no-preference including the dash setup (links
stay visible under reduced motion). Nodes are keyboard-accessible:
role=button, tabIndex, aria-label, Enter/Space select, focus-visible
brand outline.
Subtitle now names the real five radar segments. Approved fix:
results.sort() no longer mutates state during render (sorted copy in
useMemo). Linked with The Stack: holding tickers link to their stock
pages; empty state offers real example tickers, one per segment.
…inimum

Rows now sort by $/PFLOP-hr ascending (nulls last) client-side and the
badge attaches to the computed minimum, not to whatever row the server
sent first - the header's 'sorted by cheapest compute' claim is now
true by construction.
…a bucket last

The year buckets sorted by localeCompare, letting the n/a bucket land
between years. Numeric ascending now, n/a forced last with a muted
ink fill and an explaining footnote so it cannot read as a year.
…imitives, AA token bumps

Global :focus-visible ring (brand-2, offset) on every interactive
element; mouse clicks do not ring. prefers-reduced-motion kills the
ticker marquee, count-up, pulse/ping loops, and long transitions.

News ticker: pause is real state - visible pause/play button for touch
and keyboard, focus-within pauses, hover still pauses; headlines were
already real click-through links.

New trust primitives (components/Freshness.tsx): AsOf chip (hover =
exact timestamp; flips to an amber 'stale' badge when a source misses
its refresh schedule - distinct from broken), ErrorState (designed
in-place failure with retry), SrChartTable (sr-only data table so
chart content is not locked inside pixels).

Contrast audit of every token text pair on all three surfaces (WCAG
math, not eyeballs): ink-muted 3.5:1 -> #8E8B84 (4.6:1 worst case),
ink-faint 2.0:1 -> #716E67 (3.0:1+, micro de-emphasis only), chart
tick follows muted. All other pairs already passed AA.
…oss all tools

Every data page now shows an AsOf freshness chip (hover = exact
timestamp, flips to an amber stale badge when a source misses ~2x its
refresh interval). Every fetch-failure branch renders the designed
ErrorState with a working retry (react-query refetch) - no blank
panels. Loading states are layout-matched skeletons.

Screen-reader chart tables (sr-only) mirror exactly what each chart
draws: NPI gauge history, US electricity demand, the Neocloud price
history (visible series' real points, synthetic edge points excluded),
compute-frontier bars, TheTrade buildout, power-deals offtakers.

Responsive: no page scrolls horizontally at 390px (probe-verified);
Power Map renders an honest 'needs a desktop screen' card on mobile
with the summary strip, upcoming projects, and operator table still
fully usable; Neocloud overlay collapses its label gutter under 640px.

Scenario Calculator and Portfolio Overlay intentionally have no AsOf:
one is a client-side model, the other is POST-driven.
…n sync

client/src/lib/__tests__ joins the existing node:test harness (glob
extended, JSX via tsconfig.test.json so the app build is untouched).

gpu-series (100% line/func coverage): anchor vs recorded date parsing,
point ordering/dedup/garbage handling, non-positive price rejection
(log-scale safety), observed-vs-interpolated span classification with
gap boundaries, window clipping with synthetic edge interpolation,
1-2-5 log ticks incl. degenerate domains, log domain padding, label
collision solver (spacing, order, bottom overflow, unfittable stacks),
crosshair valueAt (exact/interp/out-of-range), nearest-point snapping,
sparkline domains (padding, single-point, flat-day, flat-at-zero,
missing data), date formatting.

stack-transforms (100% line/func): market-cap parsing with a
regression test pinning the old parseM bug (M != B), numeric-cap
preference, window % change and direction, table sorting with
nulls-always-last both directions and no input mutation, heatmap
grouping (ETF exclusion, unsized listing, empty groups), treemap
layout (bounds, area proportionality, group containment, degenerate
canvas), hex blending, diverging heat colors (null = neutral not
flat-day, saturation cap, monotone magnitude).

Snapshot tests (react-dom/server, no DOM needed) for the shared
loading/empty/error primitives: ErrorState with/without retry, AsOf
fresh/stale/hidden under mocked clocks, SrChartTable.

tokens-sync test enforces the promised tokens.ts <-> index.css mirror,
including all ten series slots in order.

Suite: 160 tests, 0 failures.
One subject, one screen: the cost-of-compute table and training
calculator are a derived view of the same per-model price data, so
GPU Economics is now the Economics tab of GPU Prices (URL-persisted
tabs, ?tab=economics shareable). /gpu-economics redirects into the
tab; sidebar drops from 14 to 13 entries. New ToolTabs primitive
carries the pattern for the remaining folds.
Three lenses on one question - where AI power comes from: physical
siting, procurement contracts, and the grid queue - are now one Power
tool with Map/Deals/Queue tabs. Deals and Queue render embedded (slim
intro, same body, same testids); the Leaflet map survives tab
round-trips (probe-verified markers after map->deals->map).
…tabs

Both are 'your inputs -> analysis' tools; they now share one Analyze
screen with Portfolio/Scenario tabs. Embedded modes keep the ticker
input, preset row, and sourced badges; share/URL writes are
path-aware so ?tickers= deep links work under /analyze too.
The daily digest renders as the first card on the Analysis page
(brand-bordered, collapse to ~6 lines with read-more, own error state
so a failed brief can never blank the post list). Standalone body
unchanged behind the redirect.
…tool layout

Client: /analyze registered; /gpu-economics, /power-deals, /queue,
/brief, /portfolio, /trade redirect into their host tabs. Sidebar is
9 entries (8 after the Supply Chain fold): Overview, The Stack, Power,
Compute Frontier, GPU Prices, Supply Chain, Analyze, Catalyst Tracker,
Analysis. G+1..8 remap to the new order (G+S Supply Chain interim).

Server: 301s for all six folded routes next to the existing legacy
redirects; seo.ts drops dead entries, gains /analyze (carries the
scenario FAQ structured data).

Overview module grid tells the same 7-tool story. Tests still 160/160.
The force graph SURVIVES (owner call) - and it was more honest than
the audit gave it credit for: the sim already pins each node's x to
its supply-chain stage (forceX strength 0.85). The workaround makes
that encoding legible instead of replacing it: faint stage column
guides behind the nodes, and the legend now states x = supply chain
stage, node size = connection count. Organic look kept, position
carries data.

The Stack gains 'flow' as its fourth view (lazy-loaded so the d3 sim
costs nothing until opened); URL ?view=flow overrides the stored view
so /supply-chain 301s land correctly. Timeframe toggle and the
uranium/CCJ correlation card hide in flow view (price-view concerns).

Sidebar lands at the final 8 entries: Overview, The Stack, Power,
Compute Frontier, GPU Prices, Analyze, Catalyst Tracker, Analysis.
Tests 160/160, build green, all 8 routes + all 7 fold redirects
probe-verified.
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