# `Raxol.Terminal.Capabilities.Probe`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.7.0/lib/raxol/terminal/capabilities/probe.ex#L1)

Pure probe reducer -- the clock seam (04 design §1b).

`step(state, event)` where `event` is `{:input, bytes}`, `{:clock,
monotonic_ms}`, or `:start`. **No time is read inside `step`** and no
process sleeps anywhere: the silence timeout is driven entirely by
injected clock events, so every timing case is a table row, not a
flake. The real driver wraps this with a `receive/after` loop feeding
`System.monotonic_time(:millisecond)` as `{:clock, now}` events.

Sentinel discipline (F0 §2): the batched query ends with Primary DA
(`CSI c`). Every VT-class terminal answers DA1, so read-to-sentinel
converts "did it ignore me?" into a bounded wait -- any wanted reply
that did not arrive before the sentinel is unsupported. Silence past
the deadline is the failure mode, answered with a conservative default.

Deadline policy (F0 §7 step 3): ~100 ms local, ~1 s when `$SSH_*` is
present; extended AT MOST ONCE when a first byte is seen but the
sentinel is not. After the sentinel, one drain window stays open until
the next clock event so reordered replies (a documented terminal bug)
are still parsed; after `:done`, late replies are drained, never
reclassified, and never leaked as keystrokes.

# `action`

```elixir
@type action() ::
  {:write, iodata()}
  | {:passthrough, iodata()}
  | {:extend_deadline, non_neg_integer()}
  | {:leak_free, binary()}
  | {:done, Raxol.Terminal.Capabilities.t()}
```

# `event`

```elixir
@type event() :: :start | {:input, binary()} | {:clock, integer()}
```

# `phase`

```elixir
@type phase() :: :init | :awaiting | :draining | :done
```

# `t`

```elixir
@type t() :: %Raxol.Terminal.Capabilities.Probe{
  budget_ms: pos_integer(),
  caps: Raxol.Terminal.Capabilities.t() | nil,
  deadline: integer() | nil,
  env: map(),
  extend_ms: pos_integer(),
  extended?: boolean(),
  now0: integer(),
  opts: keyword(),
  phase: phase(),
  scanner: Raxol.Terminal.Capabilities.ReplyScanner.t(),
  seen_input?: boolean(),
  tty?: boolean()
}
```

# `new`

```elixir
@spec new(
  map(),
  keyword()
) :: t()
```

Builds a probe from the env seed.

Options:

  * `:budget_ms` -- override the silence deadline (default ~100 ms
    local, ~1 s when any `$SSH_*` var is present)
  * `:extend_ms` -- the one-time extension budget (default = budget)
  * `:now_ms` -- clock origin the deadline is computed from (default 0;
    the driver passes `System.monotonic_time(:millisecond)`)
  * `:tty?` -- `false` = non-TTY path: Core-minus, ZERO queries emitted
  * `:tmux_passthrough?` -- re-issue identity/color queries wrapped for
    the outer terminal (only meaningful when `$TMUX` is set)
  * `:platform` -- forwarded to the classifier (`:windows` gates)

# `query_sequence`

```elixir
@spec query_sequence() :: binary()
```

The exact batched query the probe emits on `:start`.

# `result`

```elixir
@spec result(t()) :: :pending | {:done, Raxol.Terminal.Capabilities.t()}
```

Probe outcome: `:pending` or `{:done, %Capabilities{}}`.

# `step`

```elixir
@spec step(t(), event()) :: {t(), [action()]}
```

Advances the reducer by one event. Returns `{state, actions}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
