# Output Rendering Framework

The output rendering framework decouples command output data from its visual presentation.  Every CLI command produces structured output through a common abstraction layer, and the active **format** determines how that output is rendered to the terminal or piped to external consumers.

## Architecture

All CLI output flows through a five-stage reactive pipeline:

1. **Command Logic** opens an `OutputSession` and creates typed **element handles**.
2. **OutputSession** coordinates handles, tracks their lifecycle, and emits events to the active materialisation strategy.
3. **ElementHandles** are the producer-facing API — thread-safe, format-agnostic write interfaces.
4. **MaterializationStrategy** decides *when* and *how* to render content.
5. **Terminal / Pipe** receives the final byte stream.

## Quick Start

```python
from cleveragents.cli.output import OutputSession

with OutputSession(format="plain") as session:
    panel = session.panel("Project Details")
    panel.set_entry("Name", "local/api-service")
    panel.set_entry("Status", "active")
    panel.close()

    table = session.table(columns=["Resource", "Type"])
    table.add_row({"Resource": "api-repo", "Type": "git-checkout"})
    table.close()
```

## OutputSession

The `OutputSession` is the central coordinator.  It is used as a context manager and provides factory methods for each element type:

- `session.panel(title)` → `PanelHandle`
- `session.table(columns=[...])` → `TableHandle`
- `session.status(message)` → `StatusHandle`
- `session.progress(label)` → `ProgressHandle`

The session can be created with:

- `format` — the desired output format (`rich`, `color`, `table`, `plain`, `json`, `yaml`)
- `command` — the command name for metadata
- `strategy` — an explicit materialisation strategy (overrides format auto-selection)

## Element Handles

### PanelHandle

Key-value pair output with a title.

```python
panel = session.panel("Details")
panel.set_entry("Name", "my-project")
panel.set_entries({"A": "1", "B": "2"})
panel.remove_entry("A")
panel.close()
```

### TableHandle

Tabular data with typed columns.

```python
table = session.table(columns=["Name", "Status", "Count"])
table.add_row({"Name": "svc-1", "Status": "up", "Count": "42"})
table.add_rows([{"Name": "svc-2", "Status": "down", "Count": "0"}])
table.set_summary({"total": "42"})
table.set_sort_key("Name")
table.close()
```

### StatusHandle

Single-line status messages with level indicators.

```python
status = session.status("Processing...", level="info")
status.set_message("Done")
status.set_level("ok")
status.set_detail("All 42 items processed")
status.close()
```

### ProgressHandle

Progress bars and spinners with throttled updates (max 10 FPS in rich mode).

```python
prog = session.progress("Uploading", total=100)
prog.set_progress(50, 100)
prog.tick()
prog.increment(10)
prog.set_label("Finalising")
prog.set_indeterminate()
prog.set_step_status("validate", "done")
prog.close()
```

## Materialisation Strategies

| Format   | Strategy               | Behaviour                                      |
|----------|------------------------|-------------------------------------------------|
| `rich`   | `RichMaterializer`     | In-place terminal updates via Rich library      |
| `color`  | `ColorMaterializer`    | ANSI codes, scrolling output (no cursor moves)  |
| `table`  | `TableMaterializer`    | ASCII box-drawing for tables                    |
| `plain`  | `PlainMaterializer`    | ASCII-only, no ANSI, no Unicode box characters  |
| `json`   | `JsonMaterializer`     | Accumulate-then-serialize as JSON               |
| `yaml`   | `YamlMaterializer`     | Accumulate-then-dump as YAML                    |

### Automatic Fallback

When terminal capabilities are insufficient, the framework automatically falls back:

- `rich` → `color` → `plain` (based on TTY, ANSI, cursor support)
- `color` → `plain` (when no ANSI support)
- `table` → `plain` (when not a TTY)
- `json` / `yaml` — always available (no terminal needed)

Use `--format <value>` to explicitly set a format and skip fallback.

## Error Envelope

JSON and YAML materializers support a unified error envelope:

```json
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Resource not found",
    "details": {"id": "abc-123"}
  }
}
```

## Producer Integration Guide

### Writing Format-Agnostic Commands

Commands should create handles and write data without knowing which format is active:

```python
def my_command(session: OutputSession, items: list[Item]) -> None:
    panel = session.panel("Summary")
    panel.set_entry("Total", str(len(items)))
    panel.close()

    table = session.table(columns=["Name", "Status"])
    for item in items:
        table.add_row({"Name": item.name, "Status": item.status})
    table.close()
```

### Thread Safety

Multiple handles can be written concurrently from different threads.  The session ensures elements are rendered in declaration order even when handles close out of order.

### Backward Compatibility

The existing `format_output()` function in `cleveragents.cli.formatting` continues to work unchanged.  A new `format_output_session()` function is also available that routes through the `OutputSession` framework.
