dashboard: rebuild the page on Bootstrap 5 + jQuery + Chart.js + Font Awesome

One card per timeframe (M5/M15/H1/H4) in a Bootstrap column grid, above a
direction board that charts every timeframe's score as a diverging bar.
Each card carries its own indicator-vote chart, and the newswire gets a
headline-tone bar. Bootstrap Icons and the hand-rolled CSS gauges are gone;
dashboard.css is now the token set plus the app-specific pieces only.

Chart marks use a diverging pair validated on this surface (#0d9f7a /
#e8503a: lightness band, chroma floor, colour-vision separation, contrast).
Lighter steps of the same two hues carry the colour where it has to work as
text, and direction never travels as colour alone -- an arrow icon and the
word ride with it.

Behaviour is unchanged: same snapshot.json / news.json contract, the same
coordinated refresh loop (boundary poll, 60 s fallback, 15 s retry,
in-flight guard, stale-snapshot guard), per-timeframe change detection,
modal state preservation, HTML escaping, the news_summary.php contract and
the warming / scan-failed / weekend states. Nothing on the signal path is
touched, so no book moves.

Verified headlessly against a fixture replayed through the real signal code
(wide / laptop / tablet / mobile, both dialogs, every degraded state): no
console errors, html-validate clean.

Suite 553 -- the old static-UI test splits into one that pins the CDN stack
and one that pins a card and a chart per timeframe against
dashboard_publisher.DASHBOARD_TFS.
This commit is contained in:
Harwinder Sekhon
2026-09-21 19:25:52 -04:00
parent 32ad73c59a
commit 3005e57367
5 changed files with 1638 additions and 892 deletions
+31 -4
View File
@@ -5496,18 +5496,45 @@ def _():
["dashboard.css", "dashboard.html", "dashboard.js", "news_summary.php"] # all installed, no .tmp
@test("dashboard: static UI uses CSS gauges and a coordinated refresh loop")
@test("dashboard: static UI is Bootstrap + jQuery + Chart.js on a CDN")
def _():
# The page is served as-is (no build step), so its four libraries are
# pinned <script>/<link> tags. Only jsdelivr and cdnjs are allowed.
import re
static_dir = os.path.join(os.path.dirname(__file__), "static")
with open(os.path.join(static_dir, "dashboard.html"), encoding="utf-8") as fh:
html = fh.read()
for needle in ("bootstrap@5.3", "bootstrap.bundle.min.js", "jquery@3.7",
"chart.js@4.4", "font-awesome/6."):
assert needle in html, needle
assert "gauge.js" not in html # no local charting script survives
from urllib.parse import urlsplit
for url in re.findall(r'(?:src|href)="(https?://[^"]+)"', html):
assert urlsplit(url).netloc in {
"cdn.jsdelivr.net", "cdnjs.cloudflare.com",
"fonts.googleapis.com", "fonts.gstatic.com"}, url
@test("dashboard: one card and one chart per timeframe, coordinated refresh")
def _():
import re
static_dir = os.path.join(os.path.dirname(__file__), "static")
with open(os.path.join(static_dir, "dashboard.html"), encoding="utf-8") as fh:
html = fh.read()
with open(os.path.join(static_dir, "dashboard.js"), encoding="utf-8") as fh:
js = fh.read()
assert "chart.js" not in html.lower()
assert "gauge.js" not in html
# The four dashboard timeframes render as Bootstrap columns inside one
# row, each card carrying its own indicator canvas.
assert 'id="tf-grid"' in html and "row-cols-" in html
assert 'id="chart-direction"' in html and 'id="chart-tone"' in html
assert 'id="ind-${tf}"' in js and "cardShell" in js
assert [tf.value for tf in dpub.DASHBOARD_TFS] == \
re.search(r'const TF_ORDER = \[([^\]]+)\]', js).group(1) \
.replace('"', "").replace(" ", "").split(",")
# One coordinated refresh loop, never a polling interval.
assert "setInterval(poll" not in js
assert "FALLBACK_POLL_MS" in js and "pollInFlight" in js
assert "data-select-tf" in js and "localStorage" in js
assert "scheduleBoundaryRefresh" in js and "localStorage" in js
@test("dashboard: publish is atomic and leaves no temp file")