oc_chat
Loading...
Searching...
No Matches
oc_ui.terminal.statusline Namespace Reference

Classes

class  StatusBarState

Functions

str fmt_tokens_short (int n)
str busy_hint_text (bool busy)
int _display_width (str text)
str _truncate_to_width (str text, int columns)
str _sanitized_tail ("str | None" last_line)
str busy_label_with_activity (str label, int elapsed_ms, "str | None" last_line, int width_left)
int busy_width_left (StatusBarState state, "str | None" busy_label, int target_width)
str _tail_of ("str | None" busy_label)
tuple[str,...] _drop_order ("str | None" busy_label)
"tuple[list[str], str, set[str]]" _fit (StatusBarState state, "str | None" busy_label, int target_width, tuple[str,...] order, frozenset[str] given_up=frozenset())
str _turn_cluster (StatusBarState state, frozenset[str] drop)
list[str] _standing_markers (StatusBarState state)
str _session_cluster (StatusBarState state, frozenset[str] drop)
list[str] _build_segments (StatusBarState state, str|None busy_label, frozenset[str] drop)
tuple[list[str], str] render_status_line (StatusBarState state, str|None busy_label, int target_width)

Variables

str _TAIL_SEPARATOR = " · "
int _MIN_TAIL_COLUMNS = 8
tuple _DROPPABLE = ("effort", "cache_saved", "cache_pct", "tools", "iters")
tuple _BEHIND_THE_TAIL = ("session", "turn")

Detailed Description

Shared status-bar data model and formatter — the realtime chrome spine.

Purpose
-------
Owns the one pure snapshot type (:class:`StatusBarState`) and the one
shared string-formatting logic for the always-visible status bar that
plan 147 renders across all three UI regimes (idle prompt, streaming
Live, tool-dispatch chrome). Framework-free by design — imports
neither ``rich`` nor ``prompt_toolkit`` — so both
:class:`~oc_ui.terminal.input.PromptDriver` (prompt_toolkit) and
:class:`~oc_ui.terminal.rich_presenter.RichPresenter` (rich) render
from the SAME segment/format logic instead of hand-rolling their own
bar strings.

Responsibilities
----------------
- :class:`StatusBarState` — frozen, slotted, pure data snapshot. No
  busy/activity fields — that state is presenter-owned (see
  :meth:`~oc_ui.presenter_base.Presenter.bind_status`).
- :func:`fmt_tokens_short` — compact token-count formatting (``1.2K``).
- :func:`render_status_line` — build the ordered segments for a
  snapshot + busy label, then join/drop them to fit a target width.
- :func:`busy_label_with_activity` — compose a busy label with the
  in-flight tool call's elapsed time and last output line (plan 279).
- :func:`busy_width_left` — the columns the bar still has free once it
  has been rendered with a given busy label.
- :func:`busy_hint_text` — the dim Ctrl+C/Ctrl+D hint line shown
  adjacent to the bar.

Diagnostics
-----------
Domain: OC_UI.TERMINAL
Levels:
    L2 — lifecycle events
    L3 — semantic details
    L4 — deep tracing

Contracts
---------
- Framework-free: no ``rich`` / ``prompt_toolkit`` imports here — both
  presenters format through this module instead of redefining bar
  strings.
- ``render_status_line`` never wraps; it drops segments right-to-left
  in the pinned order (effort -> cache-saved -> cache% -> tools ->
  iters, then -- for a bar that carries an activity tail -- the session
  cluster, then the turn cluster) until the rendered line fits
  ``target_width``, then stops (the remaining core segments may still
  overflow a very narrow width).
- The activity tail OUTRANKS every droppable segment and both clusters
  (operator ruling TUI-06, 2026-09-16; amended 2026-09-17): the bar gives
  up effort, the cache percentages, the saved-token segments, the tool
  and iteration counters and then the session and turn clusters -- in
  that order -- before it will shrink the tail or drop it. The model, the
  session name, the busy label with its elapsed timer, and the standing
  markers (a live grant, the session's spend, the checkpoint latch) are
  never dropped.
- A bar with no in-flight tool call carries no tail: it stops dropping at
  the end of ``_DROPPABLE`` and renders byte-identically to the pre-280
  bar at every width.
- ``StatusBarState`` carries no busy/activity fields — presenters pass
  ``busy_label`` as a separate argument; it is never captured on the
  snapshot itself. Plan 279's tool timer and tail are composed INTO
  that busy label by :func:`busy_label_with_activity`, so the snapshot
  and the segment table are unchanged by it.
- Width comparisons count wide/fullwidth Unicode (CJK) as two terminal
  columns via :func:`unicodedata.east_asian_width`, not raw ``len()``.

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.core.formatting (lazy; format_duration for the turn, session and tool call timers)

Function Documentation

◆ _build_segments()

list[str] oc_ui.terminal.statusline._build_segments ( StatusBarState state,
str | None busy_label,
frozenset[str] drop )
protected
The bar's segments, in fixed order, with every dropped rank left out.

``drop`` holds droppable field names (:data:`_DROPPABLE`) and, for a bar
carrying an activity tail, the cluster ranks (plan 280). The model, the
session name, the busy label and the standing markers are never in it.

Definition at line 416 of file statusline.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _display_width()

int oc_ui.terminal.statusline._display_width ( str text)
protected
Approximate terminal column width — wide/fullwidth CJK counts as 2 columns.

Definition at line 178 of file statusline.py.

Here is the caller graph for this function:

◆ _drop_order()

tuple[str, ...] oc_ui.terminal.statusline._drop_order ( "str | None" busy_label)
protected
The ranks this bar may give up, in the pinned order.

A bar with no tail stops at :data:`_DROPPABLE`, which is what keeps the
no-activity bar byte-identical to the pre-280 one at every width.

Definition at line 313 of file statusline.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _fit()

"tuple[list[str], str, set[str]]" oc_ui.terminal.statusline._fit ( StatusBarState state,
"str | None" busy_label,
int target_width,
tuple[str, ...] order,
frozenset[str] given_up = frozenset() )
protected
Build the segments, then drop *order* rank by rank until the line fits.

*given_up* are ranks already conceded before the loop starts (the tail's
callers hand it the prefix it has decided to yield). Returns the kept
segments, the line, and the full set of dropped ranks.

Definition at line 324 of file statusline.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _sanitized_tail()

str oc_ui.terminal.statusline._sanitized_tail ( "str | None" last_line)
protected
*last_line* with control characters removed, ready for a Live region.

A child's output is arbitrary bytes: a carriage return would rewind the
cursor mid-bar and an ANSI escape would restyle everything after it, so the
C0/C1 ranges are dropped rather than passed through to the terminal.

Definition at line 208 of file statusline.py.

Here is the caller graph for this function:

◆ _session_cluster()

str oc_ui.terminal.statusline._session_cluster ( StatusBarState state,
frozenset[str] drop )
protected

Definition at line 396 of file statusline.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _standing_markers()

list[str] oc_ui.terminal.statusline._standing_markers ( StatusBarState state)
protected
The session cluster's never-droppable markers, in render order.

Plan 235 (SHELL-02): a live Tier-2 python grant is a standing authority.
Plan 237 (CAPS-01): money, and the honesty that has to travel with it.
Deliberately NOT in ``_DROPPABLE`` for the same reason: a narrow terminal
may lose a cache percentage, never what the session is spending, the fact
that a checkpoint has stopped asking, or the fact that a confirmation gate
is down. Plan 280 keeps them when the session cluster itself yields to the
activity tail — the cluster is droppable, these are not.

Definition at line 372 of file statusline.py.

Here is the caller graph for this function:

◆ _tail_of()

str oc_ui.terminal.statusline._tail_of ( "str | None" busy_label)
protected
The activity tail carried on *busy_label*, or ``""`` when there is none.

:func:`busy_label_with_activity` is the only thing that appends
``_TAIL_SEPARATOR`` to a busy label, so the separator IS the tail's marker
(no production label contains it: ``waiting for response…`` and
``running <tool>…`` are the two the tree builds).

Definition at line 300 of file statusline.py.

Here is the caller graph for this function:

◆ _truncate_to_width()

str oc_ui.terminal.statusline._truncate_to_width ( str text,
int columns )
protected
The longest prefix of *text* that fits in *columns* terminal columns.

Definition at line 193 of file statusline.py.

Here is the caller graph for this function:

◆ _turn_cluster()

str oc_ui.terminal.statusline._turn_cluster ( StatusBarState state,
frozenset[str] drop )
protected

Definition at line 351 of file statusline.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ busy_hint_text()

str oc_ui.terminal.statusline.busy_hint_text ( bool busy)
Return the dim hint line shown adjacent to the status bar.

*busy* — ``True`` while a turn is streaming or tools are dispatching
(the wording is verified against the two-stage SIGINT handler); Ctrl+D
is never advertised while busy since it does nothing there. ``False``
at the idle prompt.

Definition at line 165 of file statusline.py.

◆ busy_label_with_activity()

str oc_ui.terminal.statusline.busy_label_with_activity ( str label,
int elapsed_ms,
"str | None" last_line,
int width_left )
Compose the busy segment for an in-flight tool call (plan 279 P4).

Returns ``"<label> <duration>"``, plus ``" · <last line>"` when the bar has
room for it. *width_left* is the number of columns the bar still has free
once it has been rendered with the label and duration alone
(:func:`busy_width_left` computes it), so a tail added here can never push
a state segment off the line: it either fits in the space nothing else
wanted, is truncated with a visible ``…`` to fit it, or is dropped whole.

Pure and framework-free, so the truncate-then-drop behaviour is testable at
any width without a terminal. The elapsed time is rendered by
:func:`~oc_chat.core.formatting.format_duration`, the same formatter the
turn and session timers use, so all three durations on the bar read alike.

Definition at line 224 of file statusline.py.

Here is the call graph for this function:

◆ busy_width_left()

int oc_ui.terminal.statusline.busy_width_left ( StatusBarState state,
"str | None" busy_label,
int target_width )
Columns *target_width* still has free after the bar is rendered without a tail.

The counterpart to :func:`busy_label_with_activity`: render once with the
label alone to learn what the state segments actually took, then spend only
what is left on the tail. Never negative — a bar whose always-on core already
overflows a very narrow terminal has nothing to spare.

The tail outranks every droppable segment and both clusters (TUI-06), so a
bar with less than the tail's minimum room gives those up, one rank at a
time and in the pinned order, until the tail has its place — or until
nothing droppable is left, in which case the room returned stays below the
caller's floor and :func:`busy_label_with_activity` drops the tail.

Definition at line 258 of file statusline.py.

Here is the call graph for this function:

◆ fmt_tokens_short()

str oc_ui.terminal.statusline.fmt_tokens_short ( int n)
Format a token count compactly: ``1.2K``, ``430K``, ``1.5M``.

Definition at line 156 of file statusline.py.

Here is the caller graph for this function:

◆ render_status_line()

tuple[list[str], str] oc_ui.terminal.statusline.render_status_line ( StatusBarState state,
str | None busy_label,
int target_width )
Render the status bar, dropping segments right-to-left to fit *target_width*.

Returns ``(kept_segments, rendered_line)``. Never wraps: once every
droppable field (:data:`_DROPPABLE`) is gone — and, for a bar carrying an
activity tail, both clusters as well — the line is returned as-is even if
the always-on core (model/name/busy/tokens/durations) still overflows
*target_width*.

The drop order is the pinned one: effort, cache-saved, cache%, tools,
iters, and — only for the bar that carries plan 279's activity tail — the
session cluster and then the turn cluster. The tail outranks all of them
(TUI-06 ruling, 2026-09-16, amended 2026-09-17).

Definition at line 444 of file statusline.py.

Here is the call graph for this function:

Variable Documentation

◆ _BEHIND_THE_TAIL

tuple oc_ui.terminal.statusline._BEHIND_THE_TAIL = ("session", "turn")
protected

Definition at line 297 of file statusline.py.

◆ _DROPPABLE

tuple oc_ui.terminal.statusline._DROPPABLE = ("effort", "cache_saved", "cache_pct", "tools", "iters")
protected

Definition at line 291 of file statusline.py.

◆ _MIN_TAIL_COLUMNS

int oc_ui.terminal.statusline._MIN_TAIL_COLUMNS = 8
protected

Definition at line 190 of file statusline.py.

◆ _TAIL_SEPARATOR

str oc_ui.terminal.statusline._TAIL_SEPARATOR = " · "
protected

Definition at line 184 of file statusline.py.