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

Classes

class  LineIndenter

Functions

str indent_text (str text, str indent=TURN_INDENT)
str start_rule_label (str ts)
str turn_rule_label (int turn_number, str ts)
str turn_end_rule_label (int turn_number, str ts, str stats="")
list[tuple[str, str]] turn_end_rule_segments (int turn_number, str ts, str stats="")
str running_total_text (int turn_tokens, int session_tokens, "Callable[[int], str] | None" fmt=None)
list[tuple[str, str]] compose_rule (str|list[tuple[str, str]] label, int width, str trailer="", str fill=RULE_FILL)
str rule_line (str|list[tuple[str, str]] label, int width, str trailer="", str fill=RULE_FILL)
bool rule_fits (str|list[tuple[str, str]] label, int width)
str plain_box_top (int width, str title=ANSWER_BOX_TITLE, str indent=TURN_INDENT)
str plain_box_bottom (int width, str subtitle="", str indent=TURN_INDENT)

Variables

str TURN_INDENT = " "
str ANSWER_BOX_TITLE = "Assistant Answer:"
str REASONING_TITLE = "Assistant CoT:"
str RULE_FILL = "─"
str PLAIN_RULE_FILL = "·"
str PLAIN_BOX_FILL = "-"
int _MIN_RULE_FILL = 4

Detailed Description

Turn-scoped layout primitives shared by every terminal presenter (plan 218).

Purpose
-------
Plan 218 makes each chat turn read as ONE bounded, indented block:
a ``start @`` rule opens the session, the assistant's answer sits in a
box whose top border carries ``Assistant Answer:`` and whose bottom
border carries that iteration's metadata, the turn-end rule absorbs the
turn stats plus the running total, and every line between the turn
rules is indented by :data:`TURN_INDENT`.

None of that geometry is presenter-specific, so it lives here as pure
string logic: :class:`~oc_ui.terminal.rich_presenter.RichPresenter`
maps the composed segments onto ``rich`` renderables and
:class:`~oc_ui.terminal.presenter.ConsolePresenter` maps the same
segments onto ASCII marker lines. One algorithm, two skins — the plain
presenter can never drift out of structural agreement with the rich one.

Framework-free by the same reasoning as
:mod:`~oc_ui.terminal.statusline`: no ``rich`` / ``prompt_toolkit``
import, so both presenters and the prompt driver can share it.

Responsibilities
----------------
- :data:`TURN_INDENT` / :func:`indent_text` — the three-space turn indent.
- :class:`LineIndenter` — line-buffered indenting for streamed chunks,
  so a presenter that prints as it streams can still prefix whole lines.
- :func:`compose_rule` — the two-cluster rule layout (centred label plus
  a trailer centred in the right-hand fill run) used by the turn-end rule.
- :func:`start_rule_label` / :func:`turn_rule_label` /
  :func:`turn_end_rule_label` / :func:`running_total_text` — the rule
  label strings.
- :func:`plain_box_top` / :func:`plain_box_bottom` — the glyph-free
  degraded box markers for the plain presenter (P4).

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

Contracts
---------
- Pure functions: no I/O, no clock reads, no framework imports. Callers
  pass the timestamp/width they want rendered.
- :func:`compose_rule` always returns segments whose joined text is
  EXACTLY *width* columns when the label fits, and never wraps: a label
  wider than the available run is emitted un-truncated with the fill
  runs collapsed to zero rather than being cut (the same "never wrap,
  never lie" discipline :func:`~oc_ui.terminal.statusline.render_status_line`
  applies to the status bar).
- The trailer is DROPPED, not truncated, when it cannot fit beside the
  label — a partial running total would be a wrong number on screen.
- :class:`LineIndenter` is append-only and never re-emits a line it has
  already returned; ``flush`` returns whatever tail is still buffered.
- No colour/style decisions here — segments are TAGGED (``"fill"`` /
  ``"label"`` / ``"trailer"``) and each presenter maps tags onto its own
  existing style tokens, so plan 218's byte-preserved-colour pin (P3)
  stays enforceable at the presenter boundary.

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_ui.terminal.statusline (lazy; fmt_tokens_short as the default running total formatter)

Function Documentation

◆ compose_rule()

list[tuple[str, str]] oc_ui.terminal.turn_layout.compose_rule ( str | list[tuple[str, str]] label,
int width,
str trailer = "",
str fill = RULE_FILL )
Compose a rule line as tagged segments.

Geometry (plan 218 P1 element 4, read off the operator's mock): the
*label* block is centred over the full *width* exactly as the pre-218
``rich.rule.Rule`` centred its title, and the *trailer* block is then
centred within the right-hand fill run that remains. Each block is
padded with two spaces either side, matching the two-space gutter
``Rule`` already produced for a label that itself carried one space.

*label* is either a plain string or a list of ``(text, tag)`` pairs
(see :func:`turn_end_rule_segments`) when parts of the label must
keep distinct styles.

Returns a list of ``(text, tag)`` pairs where *tag* is ``"fill"``,
``"trailer"``, or whichever tag the caller supplied for a label part
(``"label"`` for a plain-string label). Callers map tags onto their
own style tokens; this function makes no colour decision.

Definition at line 233 of file turn_layout.py.

Here is the caller graph for this function:

◆ indent_text()

str oc_ui.terminal.turn_layout.indent_text ( str text,
str indent = TURN_INDENT )
Prefix every line of *text* with *indent*.

A trailing newline is preserved without gaining a phantom indented
empty line, so ``indent_text("a\\n")`` is ``"   a\\n"`` — the caller can
hand the result straight to ``print(..., end="")``.

Definition at line 109 of file turn_layout.py.

◆ plain_box_bottom()

str oc_ui.terminal.turn_layout.plain_box_bottom ( int width,
str subtitle = "",
str indent = TURN_INDENT )
ASCII closing marker for the degraded answer box.

Carries the iteration metadata in the same place the rich box's
bottom border does. Always emitted — the plain presenter closes its
box on every path too (W2).

Definition at line 340 of file turn_layout.py.

◆ plain_box_top()

str oc_ui.terminal.turn_layout.plain_box_top ( int width,
str title = ANSWER_BOX_TITLE,
str indent = TURN_INDENT )
ASCII opening marker for the degraded answer box.

Renders ``"   --- Assistant Answer: -----"`` — the same information
the rich box carries in its top border, using only characters a dumb
terminal is guaranteed to draw (W5).

Definition at line 326 of file turn_layout.py.

◆ rule_fits()

bool oc_ui.terminal.turn_layout.rule_fits ( str | list[tuple[str, str]] label,
int width )
Can *label* be absorbed into a *width*-column rule and still read as one?

Plan 218's turn-end rule swallows the whole turn-stats string, which on
a narrow terminal is longer than the terminal itself. Callers use this
to choose between the absorbed one-line rule and the pre-218 two-line
form (rule, then stats beneath): degrading to the OLD layout keeps
every figure legible, whereas letting an oversized label wrap would
split a rule across lines and cut a ``ctx:NN% of <model>`` gauge in
half. Never truncate, never wrap — degrade.

Definition at line 305 of file turn_layout.py.

◆ rule_line()

str oc_ui.terminal.turn_layout.rule_line ( str | list[tuple[str, str]] label,
int width,
str trailer = "",
str fill = RULE_FILL )
Return :func:`compose_rule`'s segments joined into one plain string.

Definition at line 294 of file turn_layout.py.

Here is the call graph for this function:

◆ running_total_text()

str oc_ui.terminal.turn_layout.running_total_text ( int turn_tokens,
int session_tokens,
"Callable[[int], str] | None" fmt = None )
The running-total trailer: ``"↻ total · 7.2K / 7.2K"``.

Reuses the ``↻`` marker and the compact token formatting the
between-turns rprompt already shows
(:meth:`~oc_ui.terminal.input.TurnStats.rprompt_text`), so the same
two numbers keep the same shape wherever they appear. *fmt* defaults
to :func:`~oc_ui.terminal.statusline.fmt_tokens_short`; it is a
parameter only so this module keeps its no-sibling-import purity in
tests.

Definition at line 212 of file turn_layout.py.

◆ start_rule_label()

str oc_ui.terminal.turn_layout.start_rule_label ( str ts)
Label for the session-opening rule: ``" start @ 02:13:32 "``.

Definition at line 171 of file turn_layout.py.

◆ turn_end_rule_label()

str oc_ui.terminal.turn_layout.turn_end_rule_label ( int turn_number,
str ts,
str stats = "" )
Label for the turn-closing rule, with the turn stats absorbed.

Plan 218 P1 element 4: the stats that used to occupy their own line
under the rule move INTO the rule text. *stats* is the presenter's
existing stats string, passed through unchanged (P3 — positions
change, styles and wording do not); an empty *stats* yields the
pre-218 label.

Definition at line 181 of file turn_layout.py.

◆ turn_end_rule_segments()

list[tuple[str, str]] oc_ui.terminal.turn_layout.turn_end_rule_segments ( int turn_number,
str ts,
str stats = "" )
:func:`turn_end_rule_label` split so the stats keep their own style.

Returns ``[(rule-label, "label"), (stats, "stats")]``. The split
exists for exactly one reason (plan 218 P3): the rule title was dim
cyan and the stats line was dim, and moving the stats INTO the rule
must not repaint them in the rule's colour. Presenters map the two
tags onto the two style tokens they already used.

Definition at line 196 of file turn_layout.py.

◆ turn_rule_label()

str oc_ui.terminal.turn_layout.turn_rule_label ( int turn_number,
str ts )
Label for the turn-opening rule: ``" turn 1 @ 02:13:37 "``.

Definition at line 176 of file turn_layout.py.

Variable Documentation

◆ _MIN_RULE_FILL

int oc_ui.terminal.turn_layout._MIN_RULE_FILL = 4
protected

Definition at line 302 of file turn_layout.py.

◆ ANSWER_BOX_TITLE

str oc_ui.terminal.turn_layout.ANSWER_BOX_TITLE = "Assistant Answer:"

Definition at line 93 of file turn_layout.py.

◆ PLAIN_BOX_FILL

str oc_ui.terminal.turn_layout.PLAIN_BOX_FILL = "-"

Definition at line 106 of file turn_layout.py.

◆ PLAIN_RULE_FILL

str oc_ui.terminal.turn_layout.PLAIN_RULE_FILL = "·"

Definition at line 102 of file turn_layout.py.

◆ REASONING_TITLE

str oc_ui.terminal.turn_layout.REASONING_TITLE = "Assistant CoT:"

Definition at line 96 of file turn_layout.py.

◆ RULE_FILL

str oc_ui.terminal.turn_layout.RULE_FILL = "─"

Definition at line 99 of file turn_layout.py.

◆ TURN_INDENT

str oc_ui.terminal.turn_layout.TURN_INDENT = " "

Definition at line 88 of file turn_layout.py.