diff --git a/CHANGELOG.md b/CHANGELOG.md index 07599d85..150b3c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +### Fixed + +- **Task notes were invisible in the panel — the API response dropped them.** The `task_to_response` serializer (used by the task list and detail endpoints the panel reads) set `dev_notes` / `qa_notes` / `quick_context` but **omitted `pr_reviewer_notes`, `doc_notes`, and `notes_structured`**, and `TaskResponse` didn't even declare `notes_structured` — so the PR-reviewer's notes, the documenter's notes, and the structured PR-review verdict were always blank in the UI no matter what the agents wrote to the DB (the structured-content write-path and obligation gates work; the data simply wasn't being serialized). The builder now returns all note sections plus the structured source of truth. (`dev_notes`/`qa_notes` on an in-flight task are still legitimately empty until the developer submits / QA reviews.) + ## [0.9.0] - 2026-06-23 ### Added diff --git a/roboco/api/schemas/tasks.py b/roboco/api/schemas/tasks.py index 6338cd09..65ebd35d 100644 --- a/roboco/api/schemas/tasks.py +++ b/roboco/api/schemas/tasks.py @@ -238,6 +238,10 @@ class TaskUpdate(BaseModel): pr_reviewer_notes: str | None = None doc_notes: str | None = None quick_context: str | None = None + # The structured source of truth (panel renders the verdict pill + sections + # from this). Must be serialized or the PR-review verdict + any structured + # rendering are blank even when the DB has them. + notes_structured: dict | None = None # Lifecycle override — privileged/admin only. Applied by the route as an # audited force-transition (so an operator can recover a task wedged in a @@ -705,7 +709,10 @@ def task_to_response(task: "TaskTable") -> TaskResponse: dev_notes=task.dev_notes, qa_notes=task.qa_notes, auditor_notes=task.auditor_notes, + pr_reviewer_notes=task.pr_reviewer_notes, + doc_notes=task.doc_notes, quick_context=task.quick_context, + notes_structured=task.notes_structured, self_verified=task.self_verified, qa_verified=task.qa_verified, branch_name=getattr(task, "branch_name", None), diff --git a/tests/unit/api/test_schemas_tasks.py b/tests/unit/api/test_schemas_tasks.py index c9823a79..497704fa 100644 --- a/tests/unit/api/test_schemas_tasks.py +++ b/tests/unit/api/test_schemas_tasks.py @@ -300,7 +300,10 @@ def _stub_task(*, with_project: bool = False) -> SimpleNamespace: dev_notes=None, qa_notes=None, auditor_notes=None, + pr_reviewer_notes=None, + doc_notes=None, quick_context=None, + notes_structured=None, self_verified=False, qa_verified=None, branch_name=None, @@ -327,6 +330,23 @@ def test_task_to_response_includes_slug_when_project_loaded() -> None: assert resp.project_slug == "proj-1" +def test_task_to_response_serializes_all_note_sections() -> None: + """Regression: pr_reviewer_notes / doc_notes / notes_structured MUST be in the + response. The builder previously omitted them, so the panel showed them blank + even when the DB had them (the recurring "notes invisible" bug).""" + stub = _stub_task() + stub.pr_reviewer_notes = "## Findings\n- looks good" + stub.doc_notes = "Updated the README" + stub.notes_structured = {"pr_review": {"verdict": "passed"}} + fake_inspector = MagicMock() + fake_inspector.unloaded = {"project"} + with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector): + resp = task_to_response(stub) # type: ignore[arg-type] + assert resp.pr_reviewer_notes == "## Findings\n- looks good" + assert resp.doc_notes == "Updated the README" + assert resp.notes_structured == {"pr_review": {"verdict": "passed"}} + + def test_task_list_to_response_returns_list() -> None: stubs = [_stub_task(), _stub_task()] fake_inspector = MagicMock()