# Web UI tool-result density

> **Opt-in, default off.** When `cli.density.mode` is `normal` (the default) or `detailed`, the web UI renders tool calls exactly as it always has: raw JSON inside a `<details>` element. Setting `cli.density.mode` to `compact` or `minimal` activates the smart-summary rendering described here.

The web UI parity implementation for #1367 (shipped in #1467) extends the existing tool-call `<details>` element with a server-computed summary body, a nested raw-output disclosure, error-class salience, compact diff rendering, and repeat collapsing — all driven by the same `cli.density.*` config the CLI uses.

## Zero-config behaviour

When `cli.density.mode` is unset, `normal`, or `detailed`:

- No summary body appears
- No `tool-error-*` CSS class is applied
- No nested raw-output disclosure is added
- The DOM is byte-identical to the pre-#1467 rendering

This matches the #1367 v3 zero-config-unchanged contract. Users who want today's behaviour do nothing.

## Compact / minimal mode

Set one of the following:

```yaml
# ~/.anteroom/config.yaml
cli:
  density:
    mode: compact          # or: minimal
    head_lines: 3          # default
    tail_lines: 2          # default
    diff_context_lines: 3  # default
    collapse_repeats: true # default
```

Or via env var:

```
AI_CHAT_CLI_DENSITY_MODE=compact
```

Once active, the web UI renders each tool call as:

1. **Summary body** — a plain-text head/tail slice of the tool output (or the diff hunk if the output starts with `@@ `).
2. **Show raw output** — a nested `<details>` that, when opened, reveals the full raw JSON exactly as today. Power-user fallback preserved.
3. **Error salience** — failed calls get a coloured left border and a leading `!` marker. Four error classes are recognised:
   - `exit_nonzero` — non-zero exit code from a shell command
   - `exception` — raised exception or generic failure
   - `timeout` — timed-out call
   - `denied` — blocked by approval, policy, or rate limit
4. **Compact diff rendering** — when `edit_file` / `write_file` outputs contain unified diff hunks (starting with `@@ `), the body renders each line with a `.tool-diff-add` / `.tool-diff-del` / `.tool-diff-context` class for coloured highlighting.
5. **Repeat collapse** — three consecutive tool calls with identical `output_shape_hash` collapse into a single `.tool-repeat-collapsed` row showing the count, e.g. `bash × 3`.

## How it works

All five features are driven by the SSE payload — no client-side heuristics. The chat router (`routers/chat.py`) calls `services/tool_result_compact.summarize()`, `derive_error_class()`, and `compute_shape_hash()` before emitting `tool_call_start` / `tool_call_end` events. The same helpers are applied at serialisation time to `GET /api/conversations/{id}` so replay matches live rendering. See `docs/api/conversations.md` for the SSE contract.

Summaries are **not persisted** to SQLite — they are computed each time the conversation is loaded, so the heuristics can evolve without a database migration.

## Parity with the CLI

The web UI consumes the same summary strings the CLI produces, which in turn come from `services/tool_result_compact.summarize()`. That means:

- Byte-for-byte equivalent summary lines (CLI Rich panel vs HTML `<details>`)
- Same four error classes (`exit_nonzero` / `exception` / `timeout` / `denied`) via `derive_error_class()`
- Same collapse trigger (≥3 identical shapes via `compute_shape_hash()`)
- Same diff format (unified diff hunks)

See `docs/cli/commands.md` for the CLI `/expand` command and related knobs.

## Security notes

- All summary text is rendered with `textContent` (never `innerHTML`). Hostile payloads in `summary`, `output_preview`, `tool_name`, or `error_class` cannot execute script.
- The `error_class` value is checked against a four-item allowlist before the `tool-error-*` CSS class is applied. Arbitrary values are discarded.
- The nested `<details>` for raw output inherits the accessibility properties of the outer `<details>` (keyboard- and screen-reader-friendly).
