{% if name_chip %}{{ name_chip }}{% endif %}{{ display_name }} {# Task 4: deterministic study kind (biological/computational/theoretical), inferred by lib/study_kind.py and wired into the template context by Task 1. Small pill beside the title, before the phase pill. #} {{ study.kind }} {% if study.phase %} {{ study.phase }} {% endif %}

{# Launch actions only (▶ / ↻). The downloads live where they're used — the ↓ figures button is at the top of the Visualizations tab, and ↓ notebook is in the Model tab; the investigation card carries them too. Keeping the header to the run/reproduce actions declutters it (they need a live backend and are hidden in the snapshot). #} {# item 110: dispatch ANY registered process-bigraph composite_id (e.g. pbg-native's v2ecoli.composites.lineage_ray_batch, item 101/109) to the remote compute backend -- independent of this study's own pinned baseline composite. Remote-pinned only, same gate as "Run current spec" (see _dispatchRemoteComposite in study-detail.js). #}
{# F1: headline pill driven by effective_status() — picks the most-downstream multi-axis field set (gate > evaluation > simulation > implementation > design > expert_review), with legacy `status` as fallback. gate_status keeps its special "gate: X" prefix because it carries the pipeline-gate verdict semantics. #} {% if study.gate_status %} gate: {{ study.gate_status }} {% elif study._effective_status %} {{ study._effective_status }} {% else %} planned {% endif %} {# Spine A3: readiness panel. Populated by study-detail.js from GET /api/report-lint (the deterministic report linter), filtered to this study. Mirrors the param-enforcement banner: surfaced, connected to its source (the linter), labeled code-computed. Empty until populated (AI-free). Repositioned into the status row (Task 4); still just a mount point — Task 5 rewires its populator. #}
{# Fable G2 (§13): "status ▾" is now the six-GATE ladder — Plan / Execution / Evidence / Quality / Decision / Release — relabeling the six status axes per §13's table (`gate_ladder`, built server-side by lib.study_page.build_gate_ladder). Each gate shows its authored state and, where a machine evaluator's value is already attached to the spec (derived_status / computed_gate_verdict — see build_gate_ladder's docstring for exactly which gates have one today), a `◆ computed:` chip + a divergence marker when authored and computed disagree. `implementation_status` maps to no gate in §13's table, so it still renders as a plain (un-gated) axis chip, same as before this task, so no authored data silently disappears from the panel. Hidden entirely when nothing is set anywhere, so legacy v3 studies stay unchanged. #} {% set _ns = namespace(any_set=false) %} {% for _g in gate_ladder %}{% if _g.authored_value or _g.computed_value or _g.viz_gap_reason %}{% set _ns.any_set = true %}{% endif %}{% endfor %} {% if study.implementation_status %}{% set _ns.any_set = true %}{% endif %} {% set _cgv = study.computed_gate_verdict %} {% if _ns.any_set or (_cgv and _cgv.diverges_from_authored) %}
status {% if _cgv and _cgv.diverges_from_authored %} ⚠ code: {{ _cgv.result }} · authored: {{ study.gate_status }} {% endif %} {% for _g in gate_ladder %} {{ _g.number }} · {{ _g.name }}{% if _g.authored_value %}{{ _g.authored_value }}{% endif %} {% endfor %} {% if study.implementation_status %} Implementation{{ study.implementation_status }} {% endif %}
{% for _g in gate_ladder %}
{{ _g.number }} · {{ _g.name }} {{ _g.authored_value or 'not set' }} {% if _g.computed_value %} ◆ computed: {{ _g.computed_value }} {% if _g.diverges %} ⚠ diverges from authored {% endif %} {% endif %} {% if _g.viz_gap_reason %} visualization gap: {{ _g.viz_gap_reason }} {% endif %}
{% endfor %} {% if study.implementation_status %}
Implementation {{ study.implementation_status }}
{% endif %}
{% endif %}
{# Composite-resolution lint banner (framework-emitters). Surfaced server-side from the live registry via composite_lookup.unresolved_study_composite_refs: any baseline/condition/simulation_set composite ref that doesn't resolve to a registered composite. This is what would have caught the autopoiesis studies 2–4 (numpy-only, no registered composite). Hidden when everything resolves. #} {% if unresolved_composites %} {% endif %} {# v3 detection: any of these fields being present (even as an empty list/dict) means the study was authored against the 8-section / 5-phase schema, and should get the v3 tab set even when the lists haven't been populated yet (e.g. immediately after seeding from a follow-up). #} {% set _is_v3 = study.purpose is defined or study.pipeline_gate is defined or (study.simulation_set is defined and study.simulation_set is not none) or study.behavior_tests is defined or study.follow_up_studies is defined or study.conclusion_logic is defined %} {# Task 4: promoted question headline — the study's purpose.question (v3) or legacy top-level question, surfaced directly above the tab nav so it reads as the study's second line, not buried in an Overview panel. Replaces the removed "Spine at a glance" (#spine-summary) table below. #} {% set _q = (study.purpose.question if study.purpose else none) or study.question %} {% if _q %}

Q {{ _q }}

{% endif %}
{# ─── v4 narrative spine. Reading order: Question & approach → Findings → Debts → Conclusion. Every field edits in place and saves via /api/study-narrative-set; empty optional fields collapse to a quiet "+ Add …" affordance (progressive disclosure). Verdict + readiness are shown once, in the header pill above — not repeated here. #} {% set _report = study.report or {} %} {% set _f0 = (study.findings or [])[0] if study.findings else None %} {# Progressive-disclosure editable prose field: filled → editor; empty → a "+ Add …" button that reveals the (already save-bound) editor. #} {% macro edit_field(label, path, value, rows=3, placeholder='') %}
{% endmacro %} {# ─── Claim → Test → Result: the three-step spine of every study. ───── #} {% set _bl = study.baseline or [] %} {% set _bt = study.behavior_tests or study.expected_behavior or [] %}

Claim

{{ study.claim or study.question or '' }}

{% if study.biological_summary %}
{{ study.biological_summary }}
{% endif %}

Test

{{ study.experiment or study.objective or '' }}

{% if _bl or _bt %}
{% if _bl %}
Model {% for b in _bl %}{{ b.name or b.composite }}{% if not loop.last %}, {% endif %}{% endfor %}
{% endif %} {% if _bt %}
Measures {% for tt in _bt %}{{ (tt.description if (tt is mapping and tt.description) else (tt.name if tt is mapping else tt)) }}{% if not loop.last %}·{% endif %}{% endfor %}
{% endif %}
{% endif %}

Result

{{ study.result or _report.main_insight or (_f0.statement if _f0 else '') or '' }}

{% if result_figure %}
{{ result_figure | safe }}
{% endif %} {% if study.findings %}

🔬 Findings — what this study taught us

{% for f in study.findings %} {# Findings may carry `statement` (interpretation-tier) or only `summary`. Skip a finding with neither, so an empty study doesn't render blank ◆ cards; and render whichever text is present. #} {% set _text = f.statement or f.summary %} {% if _text %} {% set _status = f.status or 'novel' %} {% set _glyph = {'confirms': '✓', 'partial': '◐', 'contradicts': '✗', 'novel': '◆'}.get(_status, '◆') %} {% set _ftok = (f.evidence.from_test or '').split(' ')[0] if f.evidence else '' %} {% set _rtok = (f.evidence.from_run or '').split(' ')[0] if f.evidence else '' %}
{{ _glyph }} {{ _text }} {% if f.evidence and f.evidence.from_test %} via {{ f.evidence.from_test }} {% elif f.evidence and f.evidence.from_run %} in run {{ f.evidence.from_run }} {% endif %}
{{ f.id or '' }} {{ f.kind or 'other' }} {{ _status }} literature
{% if f.evidence %} {# Spine B1: the cited test/run are CLICKABLE (anchor to its #test-/#run- card), the headline computed number divergence_factor is surfaced, provenance.run_ids are linked, and the cited test's pass_if band is inlined so "passing" is legible without hunting. #}
{% if f.evidence.observed is defined %}observed: {{ f.evidence.observed | humanize_assertion }}{% if f.evidence.units %} {{ f.evidence.units }}{% endif %} · {% endif %} {# C1: from_test cites a #bt- row that lives in the (currently hidden) Tests panel — _gotoStudyTab switches to it via the existing _setStudyTab path, then scrolls, instead of a plain href="#anchor" that silently fails on a display:none panel. #} {% if f.evidence.from_test %}via test {{ f.evidence.from_test }} · {% endif %} {% if f.evidence.from_run %}in run {{ f.evidence.from_run }}{% endif %} {% if f.evidence.window %} ({{ f.evidence.window }}){% endif %} {% if f.evidence.smoking_gun %}
smoking gun
{{ f.evidence.smoking_gun }}
{% endif %}
{% set _runids = (f.provenance or {}).run_ids %} {% if f.evidence.divergence_factor is defined or _runids %}
{% if f.evidence.divergence_factor is defined %}×{{ f.evidence.divergence_factor }} vs expected{% endif %} {% if f.evidence.divergence_factor is defined and _runids %} · {% endif %} {% if _runids %}runs: {% for rid in _runids %}{{ rid }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}
{% endif %} {% set _citedtest = (study.behavior_tests or study.expected_behavior or [])|selectattr('name','equalto', _ftok)|first %} {% if _citedtest and (_citedtest.pass_if or _citedtest.expect) %}
judged against pass_if: {{ (_citedtest.pass_if or _citedtest.expect) | humanize_assertion }}
{% endif %} {% endif %} {% if f.expected %}
{% if f.expected.range %}expected: {{ f.expected.range }} · {% endif %} {% if f.expected.threshold is defined %}threshold: {{ f.expected.threshold | humanize_assertion }} · {% endif %} {% if f.expected.cites %}cites: {% for c in f.expected.cites %}{{ c }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}
{% if f.expected.summary %}
Reference says: {{ f.expected.summary }}
{% endif %} {% endif %} {% if f.explanation %}
Why: {{ f.explanation }}
{% endif %} {% if f.expert_reference %}
Expert ref: {{ f.expert_reference.doc }}{% if f.expert_reference.section %} ({{ f.expert_reference.section }}){% endif %} {% if f.expert_reference.quote %}
{{ f.expert_reference.quote }}
{% endif %} {% if f.expert_reference.note %}
{{ f.expert_reference.note }}
{% endif %}
{% endif %} {% if f.next_action %}
→ Next: {{ f.next_action }}
{% endif %}
{% endif %} {% endfor %}
{% endif %} {% set _cv = study.conclusion_verdicts or {} %} {% set _lim = study.limitations or [] %} {% if study.confidence or _cv or _lim or study.falsifiability %}
{% if study.confidence or _cv %}
Confidence {% if study.confidence %}{{ study.confidence }}{% endif %} {% if _cv.biological_validation and _cv.biological_validation.result %}biology {{ _cv.biological_validation.result }}{% endif %} {% if _cv.explanatory_gain and _cv.explanatory_gain.result %}insight {{ _cv.explanatory_gain.result }}{% endif %}
{% endif %} {% if _lim or study.falsifiability %}
Caveat {{ _lim[0] if _lim else study.falsifiability }}
{% endif %}
{% endif %}
{# ─── Compose tab: Build + Baseline + Variants + Interventions ── #} {# ─── Simulate tab: read-only runs record ─────────────────────────── Launching lives entirely in the header buttons (Reproduce / Run current spec) now — this panel is ONLY the Simulations-DB table (sim-table.js), filtered to this study via /api/simulations?study=, populated by _loadStudySims(). No reproduce-card, no simulation_set cards, no Configure & Run mount, no remote-run form. Study-spine reorg (spec §1, §3.2): panel `data-kind` stays "simulate" (only its act-cluster membership moved, Evidence→Design) and the old "Study artifacts" strip (analysis result files + raw-data downloads) split out into the new Results/Analyses panels below — see panel-results / panel-analyses. #} {# ─── Results tab (Evidence): latest-run preview + raw simulation data downloads ──────────────────────────────────────────────────────────── Study-spine reorg (spec §1, §3.3; plan Task 4): relocated out of the Simulations panel's old "Study artifacts" strip (downloads-only, Task 1), then upgraded with a per-store PREVIEW of the study's latest run — sparkline + first/last/min/max per emitted store path, via /api/study-results -> lib/results_views.build_study_results. Full arrays stay in the downloads below (never fetched into this preview). `id="exports-downloads"` stays the jump target for the Readouts tab's pointer link (`_gotoStudyTab('results', 'exports-downloads')`). `#raw-data-download-all` is a client-side sequential-download button (populated/shown by _loadResults, triggered by _downloadAllRawExports) — same element ids as before so those loaders bind unchanged. #} {# ─── Analyses tab (Evidence): analysis-step result files ───────────────── Study-spine reorg (spec §1, §3.4): promoted out of the Simulations panel's old "Study artifacts" strip into its own Evidence panel. Reuses /api/study-analysis-outputs / -file / -zip unchanged — same element ids as before so the existing loader (_loadAnalyses, was _loadAnalysisOutputs) binds unchanged; only the markup + panel moved. #} {# ─── Audit tab (Assurance) — NEW: "is the bar high enough to trust?" ────── Study-spine reorg (spec §3.7): three groups, all filled client-side by _loadAudit (dispatched from _setStudyTab('audit')) — none are fields already sitting on `study`, all are live computes: 1. Sufficiency — viva_superpowers.test_audit.build_audit_report + gate, via GET /api/study-test-audit (lib.audit_panel_views). 2. Quality (rigor scorecard) — MOVED here from Tests (Fable G5), unchanged GET /api/study-rigor / _loadQualityChecks. 3. Reproducibility (L0-L5) — MOVED here from Tests (Fable G6), unchanged GET /api/study-audit / _loadReproducibilityChecks. Each mount degrades to an explicit unavailable(reason) message rather than an empty box (§2 R2). #} {# ─── Build tab (Assurance) — NEW: "was the pass earned honestly?" ───────── Study-spine reorg (spec §3.8): model-build loop provenance from `.pbg/loop/.json` (viva_superpowers.loop_state) — locked-tests hash, reopen trail, iteration history, state/outcome. Filled client-side by _loadBuild (dispatched from _setStudyTab('build')) via GET /api/study-loop-state (lib.loop_provenance_views). GRACEFUL empty state when a study was never run through /viva-model-build — a one-line note, never an error. #}