|
| 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) |
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)
| 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.
| 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.
| 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.
| 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.