> ## Documentation Index
> Fetch the complete documentation index at: https://docs.staging.questra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Review chat

> Browser-SoT coding-agent chat with persisted conversation history.

Review edits programmed survey content with an AI SDK coding agent (default **`openai/gpt-5.6-luna`**, reasoning **`medium`**). The **Monaco buffer is the document source of truth**. Tip revisions are created only when the user explicitly saves.

## Model

| Concern                                                                                                                                 | Owner                                         |
| --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| Document tools (`read_document`, `apply_document_patch`, …)                                                                             | Client → Monaco                               |
| Survey file tools (`list_survey_files`, `read_survey_file`, `write_survey_file`, `read_survey_spreadsheet`, `write_survey_spreadsheet`) | Server (`execute` in chat stream)             |
| Spreadsheet decode/encode (`.xls` / `.xlsx` / `.csv` / `.tsv`)                                                                          | Server SheetJS → JSON cell grids              |
| Model stream                                                                                                                            | `POST /v1/surveys/{surveyId}/chat`            |
| Chat history                                                                                                                            | Survey **conversations** (`UIMessage` upsert) |
| Tip revision                                                                                                                            | `PUT /v1/surveys/{surveyId}/content` (Save)   |

Document tools are declared without `execute` so the browser runs them against Monaco. Survey file tools run on the server against object storage (file writes persist immediately; survey source still requires explicit Save).

This path does **not** use `survey.content_edit` / WorkflowAgent turn drafts. That workflow remains available for callers that want server-side document tools and tip finalize; Review does not.

## Conversation setup

```http theme={null}
POST /v1/surveys/{surveyId}/conversations
GET  /v1/surveys/{surveyId}/conversations/{conversationId}/messages
```

Create (or reuse) a conversation, then load persisted UIMessages into the chat client.

## Send a message

```http theme={null}
POST /v1/surveys/{surveyId}/chat
Content-Type: application/json

{
  "conversation_id": "<uuid>",
  "messages": [ /* AI SDK UIMessage[] */ ]
}
```

* Response: `text/event-stream` (UIMessage SSE)
* Message ids **must be UUIDs** (clients should use `generateId: () => crypto.randomUUID()`)
* Inbound messages (including client tool results on continuations) are upserted before the model streams
* The completed assistant message(s) are upserted when the stream ends
* No content revision is created

### Composer attachments

The Review composer supports **multi-file** attachments (paperclip → upload local files or pick ready survey files). Submit stays disabled while any attachment is still preparing/uploading.

| Source                                 | Path                 | UIMessage parts                                      | Model consumption                                                  |
| -------------------------------------- | -------------------- | ---------------------------------------------------- | ------------------------------------------------------------------ |
| Local **image/\***                     | LLM-native           | `file` (`FileUIPart` with data URL)                  | Multimodal file parts — **no** survey-file create                  |
| Local **application/pdf**              | Dual                 | `file` + `data-survey-file`                          | Model sees the PDF; agent also gets `file_id` for tip/tools        |
| Local other types (docx, zip, xlsx, …) | Survey-file fallback | `data-survey-file` `{ file_id, name, content_type }` | Expanded to short model text with `file_id`; agent uses file tools |
| Existing ready survey file             | Reference only       | Same `data-survey-file`                              | Same expansion; no re-upload                                       |

Images are **not** auto-written into the file manager. If the user asks to keep a copy, the agent may call `write_survey_file` / spreadsheet write tools.

### Survey file tools

| Tool                                                   | Behavior                                                              |
| ------------------------------------------------------ | --------------------------------------------------------------------- |
| `list_survey_files`                                    | Metadata + `kind` (`spreadsheet` \| `text` \| `binary`)               |
| `read_survey_file` / `write_survey_file`               | UTF-8 text; rejects spreadsheets with a redirect to spreadsheet tools |
| `read_survey_spreadsheet` / `write_survey_spreadsheet` | SheetJS cell grids (`string[][]` per sheet); upsert by name           |

### Questionnaire tip tools

Users often attach a DOCX/PDF/text file and ask the agent to treat it as the next questionnaire. Tip assertion reuses the same `PUT /v1/surveys/{id}/questionnaire` rules (ready `file_id` → immutable revision pointer).

| Tool                         | Behavior                                                           |
| ---------------------------- | ------------------------------------------------------------------ |
| `get_questionnaire_revision` | Current tip (`revision`, `file_id`, file name) or null             |
| `set_questionnaire_revision` | Assert a ready survey `file_id` as tip (idempotent if already tip) |

The agent should call `set_questionnaire_revision` when the user clearly intends an attached/referenced file to be the questionnaire (e.g. “use this as the questionnaire”, “updated Word doc”). It should not promote incidental screenshots or sidecars unless the user says that file is the questionnaire.

### Questionnaire audit tool

`audit_against_questionnaire` is a **client tool** (no server `execute`): the browser starts `POST /v1/surveys/{id}/audits` pinned to the last saved content tip (does **not** auto-save a dirty Monaco draft) and **awaits the World run until terminal**.

| Clock             | Behavior                                                                                                                                                                                                                                                   |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **UI**            | As soon as `workflow_id` is known, a checklist mounts and streams NDJSON `audit.issue` chunks so the user can inspect (and Dismiss) while detection continues. After the run is terminal, the checklist also hydrates from `GET .../audits/{id}/findings`. |
| **Tool / model**  | `addToolOutput` runs once with `{ audit_id, workflow_id, status, issues[] }` (each issue includes a persisted `id`) — then `useChat` auto-continues so the result is persisted on the next `POST /chat` and the model can summarize or fix                 |
| **Stop / reload** | Stop cancels the World wait and closes the tool with `status: cancelled`. A page reload that leaves an open audit tool (no in-memory runner) auto-cancels it so the chat is not stuck busy.                                                                |

Each issue is `{ id, code, message, start_line?, end_line? }` (1-based lines in the **pinned content revision** when the model can locate them). The checklist shows a clickable line link that switches to the **Code** tab and reveals that range.

After detect, the agent patches the **current editor buffer** (unsaved draft is fine) with `apply_document_patch`, then calls `update_audit_finding` to PATCH each finding `resolved` or `dismissed`. It does **not** start `POST .../remediations`. Save is a separate action when the user wants a new content tip.

Fix actions that start a new chat turn wait until the current turn is idle; Dismiss and Fix both collapse the issue in place (expand to reopen). Dismiss is local and always available.

### Scenario discovery + Behavior Review auto-walk

`discover_test_scenarios` is a **client tool** (no server `execute`): the browser starts `survey.scenario_discovery` against the **tip questionnaire only** (never Monaco/program source) and awaits the World run until terminal.

| Clock            | Behavior                                                                                                                                                       |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **UI**           | A scenario list streams in as `scenario` chunks arrive. Each row has a short label, kind, `depends_on`, expandable inputs/expect, plus **Test** / **Test all** |
| **Tool / model** | `addToolOutput` returns `{ workflow_id, stream_url, status, scenarios[] }` — then chat auto-continues so the model can summarize                               |

Scenarios form a DAG via `depends_on`. **Test all** (and **Test** on a leaf, which auto-queues unmet deps) starts `survey.scenario_test`:

1. Topological order; scenarios whose deps did not **pass** are marked `blocked` and **do not open a browser**
2. Ready scenarios create a Behavior Review session + Browserbase/local CDP browser, then hand that **provisioned session** to a separate Luna `ToolLoopAgent` with a narrow toolset (`browser_act`, `get_interview_state`, `submit_verdict`, `finish_scenario`). Navigation observations are recorded automatically on page-key change. Accepted axes mint suite edges/goldens so the Regressions tab suite grows automatically.

Outcomes: `passed` | `failed` | `blocked` | `unreachable` | `error` (`blocked` is not collapsed into `failed`).

```http theme={null}
POST /v1/surveys/{surveyId}/scenario-discovery
GET  /v1/surveys/{surveyId}/scenario-discovery/runs/{runId}/stream

POST /v1/surveys/{surveyId}/scenario-tests
GET  /v1/surveys/{surveyId}/scenario-tests/runs/{runId}/stream
```

## Explicit save

```http theme={null}
PUT /v1/surveys/{surveyId}/content
```

Agent edits stay in the editor until Save. Chat reloads from `GET …/messages`. The unsaved Monaco buffer is kept in **browser localStorage** (keyed by survey + tip revision) so refresh keeps agent/human edits; Save clears that draft and advances tip. If tip advanced elsewhere, the stale local draft is discarded.

When the survey is linked (`integration_id` + `upstream_survey_id`), Save **pushes to the platform first**, then writes tip. Push failures return `502 upstream_push_failed` (tip unchanged) and surface in the editor with a **Fix with AI** action.

## Editor tabs

The Review editor pane has **Preview | Code | Files**:

| Tab         | Behavior                                                                                                                                                                                                                                                                                                                                                       |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Preview** | Upstream respondent test URL in an iframe. Disabled without `upstream_survey_id` / `external_survey_id`. State from `GET/POST /v1/surveys/{id}/preview` (`ready` / `preparing` / `blocked` / `unsupported`). Decipher resolves sync; ConfirmIt launches external-test QA and polls until ready. ConfirmIt also force-refreshes after a successful linked Save. |
| **Code**    | Monaco + agent hunk controls (this document’s source of truth).                                                                                                                                                                                                                                                                                                |
| **Files**   | Survey file list with add/remove, hosting badges, and move between Questra CDN vs platform (`visibility` chosen at create).                                                                                                                                                                                                                                    |

### Unreviewed diff stacking

Decorations compare a stable **reviewed base** (initially tip) to the live buffer. That base does **not** move when the agent applies further patches across user messages, so highlights accumulate. **Accept** advances the reviewed base to the current buffer (clears decorations without publishing tip). **Discard** restores the buffer to the reviewed base. **User Save** (including ⌘S / Ctrl+S) is always available — it accepts any stacked unreviewed hunks, then opens the save flow to publish tip / push upstream. Agent `save_survey_content` publishes tip (and refreshes Preview) **without** accepting the stack — only the user can dismiss unreviewed decorations.

## Related

* [Content edit](/workflows/content-edit) — WorkflowAgent path with server draft + tip finalize (not used by Review UI)
