Pointf5ive commited on
Commit
5432dfe
Β·
1 Parent(s): 7ae21b5

stage 3: static dashboard shell wrapper

Browse files

Scope: add render_dashboard(state) static shell with totem-shell/sidebar/main wrappers and placeholder topbar/hero/status/metrics/lower sections.

Validation: py_compile + render smoke checks passed; existing tabs/callback wiring preserved.

Files changed (1) hide show
  1. app.py +206 -12
app.py CHANGED
@@ -291,6 +291,144 @@ TOTEM_CSS = """
291
 
292
  --font-display: "Cormorant Garamond", "Playfair Display", Georgia, serif;
293
  --font-ui: Inter, Aptos, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  }
295
  """
296
 
@@ -523,6 +661,64 @@ def _clean_path(uploaded_file) -> Path:
523
  return workbook_path(uploaded_file)
524
 
525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  def _validate_workbook_path(path: Path) -> Path:
527
  if not path.exists():
528
  raise gr.Error(f"Workbook path does not exist: {path}")
@@ -1380,18 +1576,16 @@ def initial_dashboard_html() -> str:
1380
  Render dashboard shell at startup so sidebar/nav are visible immediately.
1381
  Falls back gracefully if workbook read fails.
1382
  """
1383
- try:
1384
- return dashboard_html(DEFAULT_WORKBOOK, "Dashboard ready. Click Run TOTEM Analysis to refresh metrics.")
1385
- except Exception as exc:
1386
- return (
1387
- "<div style='padding:24px;font-family:system-ui,sans-serif'>"
1388
- "<h3 style='margin:0 0 8px'>TOTEM Dashboard</h3>"
1389
- "<p style='margin:0 0 12px;color:#666'>"
1390
- "Dashboard shell is ready. Click <b>Run TOTEM Analysis</b> to load workbook data."
1391
- "</p>"
1392
- f"<p style='margin:0;color:#888;font-size:12px'>Startup note: {escape(str(exc))}</p>"
1393
- "</div>"
1394
- )
1395
 
1396
 
1397
  # ── GRADIO INTERFACE ──────────────────────────────────────────────────────────
 
291
 
292
  --font-display: "Cormorant Garamond", "Playfair Display", Georgia, serif;
293
  --font-ui: Inter, Aptos, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
294
+
295
+ min-height: 880px;
296
+ display: flex;
297
+ background: linear-gradient(180deg, #040816 0%, #07122d 100%);
298
+ color: var(--totem-ivory);
299
+ }
300
+
301
+ .totem-sidebar {
302
+ width: 256px;
303
+ flex: 0 0 256px;
304
+ min-height: 100%;
305
+ border-right: 1px solid rgba(242, 193, 78, 0.22);
306
+ background: linear-gradient(180deg, #050d24 0%, #07122d 65%, #091733 100%);
307
+ padding: 20px 18px;
308
+ }
309
+
310
+ .totem-main {
311
+ flex: 1;
312
+ min-width: 0;
313
+ padding: 20px 22px;
314
+ background: linear-gradient(180deg, #07122d 0%, #0b1633 100%);
315
+ }
316
+
317
+ .totem-main-inner {
318
+ width: 100%;
319
+ }
320
+
321
+ .totem-topbar,
322
+ .totem-hero,
323
+ .totem-status-row,
324
+ .totem-metrics-grid,
325
+ .totem-lower-grid {
326
+ border-radius: var(--radius-panel);
327
+ border: 1px solid rgba(242, 193, 78, 0.22);
328
+ background: linear-gradient(180deg, rgba(16, 26, 58, 0.96), rgba(17, 30, 68, 0.95));
329
+ margin-bottom: var(--space-4);
330
+ }
331
+
332
+ .totem-topbar {
333
+ min-height: 64px;
334
+ display: flex;
335
+ align-items: center;
336
+ justify-content: space-between;
337
+ padding: 0 var(--space-5);
338
+ }
339
+
340
+ .totem-hero {
341
+ min-height: 222px;
342
+ padding: 44px 52px;
343
+ }
344
+
345
+ .totem-status-row {
346
+ min-height: 44px;
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: space-between;
350
+ padding: 0 var(--space-5);
351
+ }
352
+
353
+ .totem-metrics-grid {
354
+ min-height: 168px;
355
+ padding: var(--space-5);
356
+ }
357
+
358
+ .totem-lower-grid {
359
+ min-height: 240px;
360
+ padding: var(--space-5);
361
+ display: grid;
362
+ grid-template-columns: 1fr 1fr;
363
+ gap: var(--space-4);
364
+ }
365
+
366
+ .totem-shell .totem-brand {
367
+ font-family: var(--font-display);
368
+ font-size: 34px;
369
+ color: var(--totem-gold);
370
+ line-height: 1.1;
371
+ margin-bottom: var(--space-6);
372
+ }
373
+
374
+ .totem-shell .totem-nav-item {
375
+ height: 48px;
376
+ display: flex;
377
+ align-items: center;
378
+ padding: 0 var(--space-4);
379
+ border-radius: 12px;
380
+ color: rgba(246, 241, 232, 0.88);
381
+ margin-bottom: var(--space-2);
382
+ border: 1px solid transparent;
383
+ font-family: var(--font-ui);
384
+ }
385
+
386
+ .totem-shell .totem-nav-item.active {
387
+ border-color: rgba(242, 193, 78, 0.45);
388
+ background: linear-gradient(90deg, rgba(242, 193, 78, 0.2), rgba(138, 77, 255, 0.2));
389
+ }
390
+
391
+ .totem-shell .totem-muted {
392
+ color: var(--totem-muted);
393
+ font-family: var(--font-ui);
394
+ }
395
+
396
+ .totem-shell .totem-placeholder {
397
+ border: 1px dashed rgba(174, 183, 204, 0.45);
398
+ border-radius: var(--radius-card);
399
+ min-height: 78px;
400
+ display: grid;
401
+ place-items: center;
402
+ color: rgba(174, 183, 204, 0.95);
403
+ font-family: var(--font-ui);
404
+ }
405
+
406
+ .totem-shell .totem-row {
407
+ display: grid;
408
+ grid-template-columns: repeat(5, minmax(0, 1fr));
409
+ gap: var(--space-3);
410
+ }
411
+
412
+ @media (max-width: 1200px) {
413
+ .totem-shell .totem-row {
414
+ grid-template-columns: repeat(3, minmax(0, 1fr));
415
+ }
416
+ .totem-lower-grid {
417
+ grid-template-columns: 1fr;
418
+ }
419
+ }
420
+
421
+ @media (max-width: 900px) {
422
+ .totem-shell {
423
+ flex-direction: column;
424
+ }
425
+ .totem-sidebar {
426
+ width: 100%;
427
+ flex-basis: auto;
428
+ }
429
+ .totem-shell .totem-row {
430
+ grid-template-columns: 1fr;
431
+ }
432
  }
433
  """
434
 
 
661
  return workbook_path(uploaded_file)
662
 
663
 
664
+ def render_dashboard(state: dict) -> str:
665
+ """
666
+ Stage 3 static shell wrapper.
667
+ Callbacks are intentionally not rewired in this stage.
668
+ """
669
+ s = normalize_dashboard_state(state)
670
+ project_name = esc(s.get("project_name", "Editorial Workspace"))
671
+ analysis_status = esc(s.get("analysis_status", "idle")).upper()
672
+ signal = int(s.get("totem_signal", 0) or 0)
673
+ return f"""
674
+ <div class="totem-shell">
675
+ <aside class="totem-sidebar">
676
+ <div class="totem-brand">TOTEM Studio</div>
677
+ <div class="totem-nav-item active">Home</div>
678
+ <div class="totem-nav-item">Projects</div>
679
+ <div class="totem-nav-item">Workbook Upload</div>
680
+ <div class="totem-nav-item">TOTEM Analytics</div>
681
+ <div class="totem-nav-item">Revision Queue</div>
682
+ <div class="totem-nav-item">Risk Clusters</div>
683
+ <div class="totem-nav-item">Codex Extractor</div>
684
+ <div class="totem-nav-item">Export</div>
685
+ </aside>
686
+ <main class="totem-main">
687
+ <div class="totem-main-inner">
688
+ <section class="totem-topbar">
689
+ <div>{project_name}</div>
690
+ <div class="totem-muted">STATUS: {analysis_status}</div>
691
+ </section>
692
+ <section class="totem-hero">
693
+ <h2 style="margin:0;font-family:var(--font-display);font-size:44px;color:var(--totem-gold);">TOTEM Studio</h2>
694
+ <p style="margin:10px 0 0 0;font-size:22px;color:rgba(246,241,232,.92);font-family:var(--font-ui);">
695
+ Data-driven insight for stronger stories.
696
+ </p>
697
+ <p class="totem-muted" style="margin-top:14px;">TOTEM Signal {signal}%</p>
698
+ </section>
699
+ <section class="totem-status-row">
700
+ <div>Dashboard shell ready. Stage 3 static layout.</div>
701
+ <div class="totem-muted">Signal {signal}%</div>
702
+ </section>
703
+ <section class="totem-metrics-grid">
704
+ <div class="totem-row">
705
+ <div class="totem-placeholder">Metrics Placeholder</div>
706
+ <div class="totem-placeholder">Metrics Placeholder</div>
707
+ <div class="totem-placeholder">Metrics Placeholder</div>
708
+ <div class="totem-placeholder">Metrics Placeholder</div>
709
+ <div class="totem-placeholder">Metrics Placeholder</div>
710
+ </div>
711
+ </section>
712
+ <section class="totem-lower-grid">
713
+ <div class="totem-placeholder">Revision Queue Placeholder</div>
714
+ <div class="totem-placeholder">Risk Clusters Placeholder</div>
715
+ </section>
716
+ </div>
717
+ </main>
718
+ </div>
719
+ """
720
+
721
+
722
  def _validate_workbook_path(path: Path) -> Path:
723
  if not path.exists():
724
  raise gr.Error(f"Workbook path does not exist: {path}")
 
1576
  Render dashboard shell at startup so sidebar/nav are visible immediately.
1577
  Falls back gracefully if workbook read fails.
1578
  """
1579
+ state = get_initial_dashboard_state()
1580
+ state["workbook_loaded"] = bool(DEFAULT_WORKBOOK.exists())
1581
+ state["analysis_status"] = "ready" if state["workbook_loaded"] else "idle"
1582
+ state["project_name"] = "Editorial Workspace"
1583
+ state["totem_signal"] = compute_totem_signal(
1584
+ state["metrics"],
1585
+ workbook_loaded=state["workbook_loaded"],
1586
+ analysis_timestamp=state["last_analysis_at"],
1587
+ )
1588
+ return render_dashboard(state)
 
 
1589
 
1590
 
1591
  # ── GRADIO INTERFACE ──────────────────────────────────────────────────────────