import inspect import json from pathlib import Path from PIL import Image import app from sovereign_bench.models import AgentTurn, EvidenceItem, JurorVote, TrialEvent, Verdict OLD_CARD_CLASSES = [ "paper-panel", "juror-panel", "mind-panel", "empty-state", "trial-downloads", ] def _event_with_lower_tab_data() -> TrialEvent: evidence = EvidenceItem( id="E1", title="Ledger entry", source="Archive", excerpt="A short exhibit excerpt.", supports="claimant", reliability=0.82, note="Useful but incomplete.", ) vote = JurorVote( juror="Karl Marx", persona=app.JUROR_PERSONAS["Karl Marx"], vote="liable", reason="The exhibit supports the claim.", evidence_ids=["E1"], ) return TrialEvent( phase="deliberation", title="Jury weighs the record", body="The jury reviews the record.", turns=[ AgentTurn( agent="Nemotron Jury", role="juror panel", content="The jurors compare E1 and state their votes.", model="nvidia/Nemotron-Orchestrator-8B", confidence=0.84, input="SYSTEM:\nYou are the jury.\n\nUSER:\nWeigh E1 and explain the vote.", ) ], evidence=[evidence], votes=[vote], trace={"mode": "test"}, ) def _speaker_event(agent: str, phase: str = "questions") -> TrialEvent: return TrialEvent( phase=phase, title=f"{agent} speaks", body="A single speaker takes the floor.", turns=[ AgentTurn( agent=agent, role="test speaker", content=f"{agent} has the visible floor.", model="test-model", confidence=0.9, input="SYSTEM:\nTest prompt.", ) ], ) def _verdict_event(finding: str = "liable") -> TrialEvent: return TrialEvent( phase="verdict", title="The Court Announces Judgment", body="Judgment is announced.", verdict=Verdict( finding=finding, decree="The court enters the final judgment.", rationale="The jury majority decides the record.", evidence_ids=["E1"], uncertainty="Some uncertainty remains.", remedy="Record the judgment.", ), turns=[ AgentTurn( agent=app.JUDGE_NAME, role="verdict writer", content="The judgment of the court is guilty.", model="test-model", confidence=0.9, input="SYSTEM:\nAnnounce verdict.", ) ], ) def test_lower_tab_renderers_emit_plain_text_classes(): event = _event_with_lower_tab_data() html = "\n".join( [ app.render_evidence([]), app.render_evidence([event]), app.render_jurors([]), app.render_jurors([event]), app.render_mind([], True), app.render_mind([event], True), app.render_mind([event], False), ] ) for class_name in OLD_CARD_CLASSES: assert class_name not in html assert "drawer-text-block" in html assert "drawer-empty" in html assert "mind-text" in html def test_download_controls_are_not_wired_into_app(): source = inspect.getsource(app.build_app) assert "DownloadButton" not in source assert "Download decree" not in source assert "Download agent trace" not in source def test_case_dropdown_only_exposes_demo_and_custom_cases(): assert list(app.CASE_OPTIONS) == ["Trial of Socrates", "Greg Heffley vs Mom", "Custom"] assert "The People v. Barnaby Buttons" not in app.CASE_OPTIONS assert "Live Search Tribunal" not in app.CASE_OPTIONS def test_courtroom_splits_six_jurors_between_side_benches(): html = app.render_court([_event_with_lower_tab_data()], started=True) assert "jury-benches left" in html assert "jury-benches right" in html assert html.count("Input" in html assert "tooltip-io-label'>Output" in html assert "Click to open full thread" in html assert "class='ai-thread-modal'" in html assert "thread-block'>SYSTEM:" in html assert "The jurors compare E1 and state their votes." in html assert "href='#ai-thread-karl-marx'" in html def test_courtroom_renders_historical_judge_and_juror_assets(): html = app.render_court([_event_with_lower_tab_data()], started=True) assert "Marcus Aurelius" in html assert "assets/characters/marcus-aurelius.png" in html assert " 0, f"{path} has no fully transparent pixels" assert histogram[255] > 0, f"{path} has no fully opaque prop pixels" def test_latest_speaker_sets_stage_class_and_speech_bubble(): html = app.render_court([_speaker_event("Mike OSS", phase="claims")], started=True) assert "speaker-auric" in html assert "class='speech-bubble active-dialogue speaker-auric'" in html assert "data-speaker='Mike OSS'" in html assert "Mike OSS" in html assert "test speaker" in html assert "Mike OSS has the visible floor." in html assert "puppet auric active walking" in html assert "puppet sable active" not in html assert html.count("class='speech-bubble") == 1 assert html.find("class='foreground-props'") < html.find("class='speech-bubble active-dialogue") assert ".speech-bubble.active-dialogue,\n.speech-bubble.active-dialogue * {\n color: #141413 !important;\n}" in app.CSS assert "border: 2px solid #141413;" in app.CSS assert "font-size: 12px;" in app.CSS def test_speech_bubble_uses_full_turn_content_not_event_body(): long_text = " ".join(["The record speaks plainly"] * 18) + " with a final visible phrase." event = TrialEvent( phase="questions", title="Counsel answers", body="Narration only, not spoken dialogue.", turns=[ AgentTurn( agent="Harvey Vector", role="respondent advocate", content=long_text, model="test-model", confidence=0.9, ) ], ) html = app.render_court([event], started=True) bubble = html[html.index("