"""T98 (Phase 4): drawer phase-4 bundle. Five sub-features extending the chat drawer: * T98.1 — branching UI (create / switch / from-turn). * T98.2 — significance-review panel (distribution + significance edits). * T98.3 — hide-from-view toggle (per-turn, via ``manual_edit`` projector branch ``turn_hidden``). * T98.4 — surgical delete with cascade preview (preview modal + rewind execution against a target turn). * T98.5 — remaining v1 edits (chat narrative_anchor + weather). Tests follow the T59 pattern in ``tests/test_drawer_events_threads_skip.py`` — a TestClient against the real FastAPI app with a per-test temp DB. """ from __future__ import annotations from pathlib import Path import pytest from fastapi.testclient import TestClient from chat.app import app from chat.db.connection import open_db from chat.eventlog.log import append_and_apply, append_event from chat.eventlog.projector import project @pytest.fixture def client(tmp_path, monkeypatch): cfg = tmp_path / "config.toml" cfg.write_text('featherless_api_key = "test"\n') monkeypatch.setenv("CHAT_CONFIG_PATH", str(cfg)) db = tmp_path / "test.db" monkeypatch.setenv("CHAT_DB_PATH", str(db)) with TestClient(app) as c: if hasattr(app.state, "background_worker"): app.state.background_worker.enabled = False yield c def _bot_payload(bot_id: str, name: str) -> dict: return { "id": bot_id, "name": name, "persona": "...", "voice_samples": [], "traits": [], "backstory": "", "initial_relationship_to_you": "", "kickoff_prose": "", } def _seed_chat(db: Path, *, with_scene: bool = True) -> int: """Seed a chat hosted by ``bot_a``; return the latest event id (chat_created).""" with open_db(db) as conn: append_event(conn, kind="bot_authored", payload=_bot_payload("bot_a", "BotA")) append_event( conn, kind="you_authored", payload={"name": "Me", "pronouns": "they/them", "persona": ""}, ) chat_event_id = append_event( conn, kind="chat_created", payload={ "id": "chat_bot_a", "host_bot_id": "bot_a", "initial_time": "2026-04-26T20:00:00+00:00", "narrative_anchor": "Day 1", "weather": "", }, ) if with_scene: append_event( conn, kind="scene_opened", payload={ "chat_id": "chat_bot_a", "container_id": None, "started_at": "2026-04-26T20:00:00+00:00", "participants": ["you", "bot_a"], }, ) project(conn) return chat_event_id # --------------------------------------------------------------------------- # T98.1 — branching UI. # --------------------------------------------------------------------------- def test_t98_1_create_branch_emits_branch_created_and_renders(client, tmp_path): db = tmp_path / "test.db" seed_id = _seed_chat(db) response = client.post( "/chats/chat_bot_a/drawer/branch/create", data={"name": "experiment_a", "origin_event_id": str(seed_id)}, ) assert response.status_code == 200 with open_db(db) as conn: rows = conn.execute( "SELECT COUNT(*) FROM event_log WHERE kind = 'branch_created'" ).fetchone() assert rows[0] == 1 from chat.state.branches import get_branch b = get_branch(conn, "experiment_a") assert b is not None assert b["origin_event_id"] == seed_id assert b["chat_id"] == "chat_bot_a" # Drawer partial lists the new branch. body = response.text assert "