# The inspector

The inspector shows what a running app does. `surf dev` starts it, for the web target and for the desktop target. [ADR-0105](https://git.solenovis.com/henning.liemant/surface/src/branch/main/docs/adr/0105-the-dev-tools-inspector.md) gives the reasons for this design.

## Open the panel

1. Start the app with `surf dev`, or with `surf dev --target desktop`.
2. Find the line `surf dev: inspector at http://127.0.0.1:8000/__inspector/` in the output.
3. Open this address in a browser.

A desktop app has the same panel, in a browser. The default port of a desktop app is 8000. When another program has this port, `surf dev` chooses a free port and prints it. Use `--port` to choose a port.

The panel listens only on the loopback address, so other machines cannot reach it.

## What the panel shows

| Part | What it shows |
|---|---|
| Tree | The components and their elements, in tree order. An element shows its own text. |
| Values | The props, the states and the derived values of the selected component. For a selected element, the values of the component that owns it. |
| Layout box | The position, the size, the padding, the margin, the border and the gap of the selected element, in logical px. For a selected component, the box of its first element. |
| Frame timings | The number of frames, the average and worst time, and a bar for each frame. The panel has the last 120 frames that changed something. |
| Redraw regions | The last 60 regions, the newest first. |

The sides of a box have the order top, right, bottom, left. A gap has the order row gap, column gap.

### Select an element

- Press a row of the tree. The app marks the element.
- Press **Pick**, and move the pointer over the app. The app marks the element under the pointer. Press the element to select it. The press does not reach the app. Press Escape to stop.

### See the redraw regions

Press **Redraw regions**. The app then shows each region for 300 ms. The marks and the regions are not part of the app. They are not in its tree, and a mark is not a redraw region.

### Read a value

A value shows as text:

| Value | Text |
|---|---|
| `Int`, `Float`, `Bool` | `5`, `2.5`, `true` |
| `String` | `"first"` |
| No value | `none` |
| A list | `["a", "b"]` |
| A map or a `Set` | `Map {"p": 1}` |
| A record | `{x: 1, y: 2.5}` |
| An enum | `.idle`, `.running(level: 3)` |
| `Bytes` | `Bytes(16)` |
| A function, a task | `fn`, `task` |

A part deeper than 6 levels shows as `...`, and a text stops after 2000 characters.

The text `(not computed)` means that nothing read the derived value after its last change. The inspector runs no code of the app, so it does not compute the value.

## What it costs

A release build does not have the inspector. `tests/inspector-release.sh` proves this for the web target and for the desktop target.

The agent in the app has no hooks while no panel is open. A panel attaches with its first poll. The agent removes its hooks 3 seconds after the last poll, for example after you close the panel.

## The protocol

Three parts talk to each other. All text is JSON in UTF-8, and all connections stay on the loopback address.

| Part | Where it runs |
|---|---|
| Panel | A browser. Its source is in `crates/surf-cli/src/inspector/panel`. `surf` compiles it for the first request. |
| Broker | The `surf` process: `crates/surf-cli/src/inspector/mod.rs`. |
| Agent | The app: `agent.js` on the web, and `crates/surf-desktop/src/dev/inspector.rs` on the desktop. |

The broker refuses a request with a `Host` header that is not a loopback name, or with an `Origin` header of another address. It also refuses a `Sec-Fetch-Site` header other than `same-origin` or `none`, because a browser sends no `Origin` for `<script src>`. Each response has `Cross-Origin-Resource-Policy: same-origin`. [Security](https://surface.solenovis.com/docs/security.md), section 5, has the rules of `surf dev`.

### Paths

Each path starts with `/__inspector/`.

| Request | Who sends it | Function |
|---|---|---|
| `GET ` (the path alone) | Browser | The page of the panel. |
| `GET app.js`, `GET app.css` | Browser | The compiled panel. |
| `GET agent.js` | Web page | The web agent. A page loads it for the first message. |
| `GET poll?after=N` | Panel | The events after number `N`. The answer comes after an event, or after 1 second. A poll also tells the broker that the panel is open. |
| `POST call` | Panel | Sends a request to the agent. The answer is the `result`, or status 502 with the text of the error. |
| `GET agent` | Dev host | The stream of messages for the agent. |
| `POST reply` | Agent | An answer or an event. |

A web page gets its messages on `/events`, the stream of hot reload. The dev host uses `GET agent`. The frames are the same: `event: inspect`, then one `data:` line with a JSON message. With more than one app, the newest app gets the messages.

The answer of a poll:

```json
{ "protocol": 1, "target": "web", "connected": true, "next": 12, "events": [{ "event": "changed" }] }
```

### Messages to the agent

A message has the field `method`. The broker adds `seq`, the number of the request. `attach` and `detach` come from the broker, and they get no answer.

| Method | Fields | Result |
|---|---|---|
| `attach` | | The agent adds its hooks, and sends the event `attached`. |
| `detach` | | The agent removes its hooks, its marks and its records. |
| `tree` | | `{ rows }` |
| `inspect` | `id` | `{ id, name, component, values, box }` |
| `select` | `id`, or `null` for no selection | `null`. The app marks the element. |
| `pick` | `on` | `null`. Turns the pick mode on or off. |
| `flash` | `on` | `null`. Shows the redraw regions in the app, or stops. |
| `frames` | | `{ frames }` |
| `regions` | | `{ regions }` |

An answer is `{ "seq": 7, "result": ... }` or `{ "seq": 7, "error": "text" }`. Without `attach`, each method gives the error `no panel is attached`.

| Record | Fields |
|---|---|
| A row | `id`, `depth`, `kind` (`component` or `element`), `name`, `text` |
| A value | `name`, `kind` (`prop`, `state` or `derived`), `ty`, `value` |
| A box | `x`, `y`, `width`, `height`, `padding`, `margin`, `border`, `gap`. It is `null` for a component with no element. |
| A frame | `n`, `at`, `update`, `render` |
| A region | `n`, `at`, `x`, `y`, `width`, `height` |

- An `id` is text. It stays the same for the life of its element or component, and it has no other meaning.
- `n` counts the frames and the regions of a session.
- `at` is the time in milliseconds after `attach`.
- `update` is the time of the batches or events in milliseconds, and `render` is the time of the frame in milliseconds.

### Events from the agent

| Event | Fields | Meaning |
|---|---|---|
| `attached` | `protocol`, `target` | The hooks are ready. |
| `changed` | | The tree or a value can have changed. It comes at most once in 200 ms. |
| `picked` | `id`, or `null` after Escape | The pick mode ended. |
| `reset` | | A new app took the place of the old app, for example after a reload. Each `id` is old. |

The broker also adds `reset` when an app connects during a session, and when an app goes away.

## Add to the inspector

Each change of the protocol changes three files: `agent.js`, `crates/surf-desktop/src/dev/inspector.rs` and `panel/api.surfs`.

Use these tests:

- `cargo test -p surf-cli inspector`: the broker, and the compile of the panel.
- `cargo test -p surf-desktop --features dev inspector`: the desktop agent.
- `tests/browser/inspector.spec.js`: the panel and the web agent, in Chromium.
- `tests/inspector-desktop.sh`: a real window, on a Mac.
- `tests/inspector-release.sh`: the size rule.
