bit-forecast-arena / src /ui /chrome.py
Bit-Trading-Company's picture
CI deploy 4bde701a
f1e8b0d verified
Raw
History Blame Contribute Delete
8.88 kB
"""The Arena's bridge vocabulary and its own CSS.
Everything shared with the other Spaces -- the sidebar, the command palette,
the dialogs, the design system's stylesheets, the click-bridge mechanism --
comes from `bit_ui`. This module is the thin Arena-specific layer: the actions
this Space accepts, and CSS for the parts of the design only this Space has.
"""
from __future__ import annotations
from bit_ui import bridge, theme
# Actions this Space accepts, on top of the ones bit-ui's chrome emits. An
# action off this list is dropped by the bridge, so a hand-crafted click cannot
# reach a code path the UI does not offer.
ARENA_KEYS = frozenset({
"mode", # playground | matchup
"pick", # select/deselect a model card
"asset", # choose an asset
"tf", # choose a timeframe
"hz", # choose a horizon
"gear", # toggle the sampling popover
"ghosts", # toggle sampled-path rendering
"run", # issue the forecast
"cls", # standings asset-class filter
"stand", # a standings row: deep-link into playground
"hfid", # the enrollment id box (arrives live as hfid=<text>)
"family", # the enrollment adapter-family choice
"enroll", # run the smoke test and enroll
"backfill", # contribute a backfill run
"reopen", # re-open an archived forecast from the log
"dismiss", # dismiss an error banner
"deeplink", # ?model=<slug> on first load: preselect and badge it
})
# `sidebar` is deliberately absent: collapsing is a client-side attribute flip
# in bit_ui.bridge, not a server action. Routing it through Gradio would put a
# full-page re-render behind a purely visual toggle.
ALLOWED_KEYS = (bridge.SHARED_KEYS - {"sidebar"}) | ARENA_KEYS
emit = bridge.make_emitter(ALLOWED_KEYS)
def parse_action(raw):
return bridge.parse_action(raw, ALLOWED_KEYS)
# --------------------------------------------------------------------------
# Arena-only CSS
# --------------------------------------------------------------------------
#
# Two kinds of rule live here.
#
# Layout: the design pins its zones in pixels (`railW:304px`) and switches at
# widths Gradio knows nothing about. Those are media queries rather than the
# design's inline JS-computed values, because the server cannot know the
# viewport and a round trip to find out would be absurd for a layout breakpoint.
#
# Repeated rows: the design's inline styles are the most faithful port for
# markup rendered once, but a model card carries ~700 bytes of inline style and
# there are a dozen of them, re-sent on every interaction. The classes below
# hold the design's exact values, just relocated.
ARENA_CSS = """
/* ---- the sign-in button ----------------------------------------------- */
/*
* Gradio's own component, floated into the header's right-hand slot. Keyed off
* its elem_id rather than a body-rooted selector, because Gradio re-emits every
* rule prefixed `.gradio-container … .contain` and a body-rooted one cannot be
* prefixed -- only the weak unprefixed copy survives and it loses.
*/
#bit-login {
position: fixed; top: 9px; right: 14px; z-index: 60;
width: auto; min-width: 0; max-width: 260px;
font-family: var(--font-mono); font-size: var(--text-2xs);
text-transform: uppercase; letter-spacing: var(--tracking-wide);
background: var(--bg-raised); color: var(--text-secondary);
border: 1px solid var(--border-default); border-radius: 0;
padding: 4px 10px; box-shadow: none;
}
#bit-login:hover {
border-color: var(--accent-amber); color: var(--accent-amber-strong);
}
/* ---- zone layout ------------------------------------------------------ */
.fa-zones { display:flex; align-items:flex-start; gap:12px; flex-wrap:nowrap; }
.fa-main { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:12px; }
.fa-rail { flex:0 0 304px; width:304px; min-width:0;
display:flex; flex-direction:column; gap:12px; }
.fa-picker { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:0; }
.fa-foot { display:grid; grid-template-columns:1.1fr 0.8fr 0.8fr 1.6fr; gap:0; }
@media (max-width:1400px) {
.fa-zones { flex-wrap:wrap; }
.fa-main { flex:1 1 100%; width:100%; }
.fa-rail { flex:1 1 100%; width:100%; }
}
@media (max-width:1180px) { .fa-picker { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:900px) { .fa-foot { grid-template-columns:1fr; } }
/* ---- model card (repeated) -------------------------------------------- */
.fa-card { text-align:left; display:flex; flex-direction:column; gap:7px;
padding:10px 11px; border:0; border-right:1px solid var(--border-subtle);
border-top:3px solid transparent; cursor:pointer;
color:var(--text-primary); background:transparent;
transition:background 1s ease; }
.fa-card:hover { background:var(--bg-raised); }
.fa-card.on { background:var(--bg-raised); }
.fa-card-top { display:flex; align-items:center; gap:7px; }
.fa-card-name { font-family:var(--font-styrene); font-size:var(--text-md);
text-transform:uppercase; letter-spacing:var(--tracking-wide);
color:var(--text-secondary);
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.fa-card.on .fa-card-name { color:var(--text-primary); }
.fa-card-caps { display:flex; flex-wrap:wrap; gap:4px; }
.fa-card-cal { display:flex; align-items:center; gap:6px;
border-top:1px solid var(--border-subtle); padding-top:6px; }
.fa-chk { margin-left:auto; display:inline-flex; align-items:center;
justify-content:center; width:16px; height:16px;
border:1px solid var(--border-default); background:transparent;
color:var(--text-tertiary); font-family:var(--font-mono);
font-size:var(--text-2xs); line-height:1; }
.fa-tag { font-size:var(--text-2xs); padding:1px 5px; white-space:nowrap;
border:1px solid var(--border-default); background:transparent; }
/* ---- standings row (repeated) ----------------------------------------- */
.fa-stand { width:100%; text-align:left; display:flex; align-items:center;
gap:8px; padding:6px 10px; background:transparent; border:0;
border-bottom:1px solid var(--border-subtle);
border-left:2px solid transparent; cursor:pointer;
color:var(--text-primary); transition:background 1s ease; }
.fa-stand:hover { background:var(--bg-raised); }
.fa-stand.on { background:var(--bg-raised); }
.fa-stand-mid { flex:1 1 auto; min-width:0; display:flex;
flex-direction:column; gap:1px; }
.fa-stand-name { font-size:var(--text-xs); white-space:nowrap;
overflow:hidden; text-overflow:ellipsis; }
/* ---- track-record thumbnails (repeated) -------------------------------- */
.fa-trcell { flex:0 0 auto; width:112px; padding:8px 9px;
border-right:1px solid var(--border-subtle);
display:flex; flex-direction:column; gap:5px; }
.fa-trcell.miss { background:var(--bg-raised); }
.fa-trstrip { display:flex; gap:0; overflow-x:auto; }
/* ---- pill buttons (repeated) ------------------------------------------ */
.fa-pill { padding:3px 8px; background:transparent;
border:1px solid var(--border-default); color:var(--text-secondary);
cursor:pointer; font-family:var(--font-mono); font-size:var(--text-xs);
transition:background 1s ease,color 1s ease; }
.fa-pill.on { background:var(--bg-raised); border-color:var(--accent-amber);
color:var(--accent-amber-strong); }
.fa-seg { padding:3px 10px; background:transparent;
border:1px solid var(--border-default); border-left-width:0;
color:var(--text-secondary); cursor:pointer;
font-family:var(--font-mono); font-size:var(--text-xs);
transition:background 1s ease,color 1s ease; }
.fa-seg.first { border-left-width:1px; }
.fa-seg.on { background:var(--accent-amber); color:var(--stone-950); }
/* The chart is a fixed-ratio SVG; letting it size from content makes the
iframe ratchet, which is the failure `bit_ui.theme.find_forced_vh` exists
to catch. `width:100%;height:auto` on a viewBox does the right thing. */
.fa-chart svg { display:block; width:100%; height:auto; }
.fa-chart { position:relative; background:var(--bg-sunken);
display:flex; align-items:stretch; }
.fa-axis { flex:0 0 auto; width:66px; position:relative;
border-left:1px solid var(--border-subtle); }
"""
def full_css(extra: str = "") -> str:
"""bit-ui's shared CSS plus the Arena's own.
Calling `theme.full_css()` directly is the mistake to avoid: it returns the
shared chrome only and silently drops everything above, which reads as a
layout bug rather than a missing stylesheet.
"""
return theme.full_css(ARENA_CSS + extra)