On this page
Lays the deletion checklist's column of session blocks into a window that may be too short, as styled lines a screen draws without the builder touching a terminal.
#claudewheel.session_list
#claudewheel.session_list
The scrolling column of session blocks the deletion checklist is drawn with.
One screen uses it: the checklist deletion presents of everything holding a profile (:mod:claudewheel.deletion_checklist). The looking and the scrolling are kept here rather than inside that screen, so the block layout is testable on its own -- built on :func:claudewheel.vertical_viewport.compute_viewport for the arithmetic and :func:claudewheel.session_rows.format_row for the text.
The machine-wide sessions screen is a different presentation entirely -- a framed table of one line per session (:mod:claudewheel.sessions_table) -- and borrows only :func:move_focus from here.
The frame builder is pure: rows, dimensions, a clock and an identity in, a list of :class:FrameLine out. Each line carries a style naming what it is (title, hint, an ordinary row line, the focused row, or whatever a row declared for its state line), and :func:render_frame is the only place a style becomes an escape sequence. That split is what lets the whole layout -- scrolling, clipping, truncation, the current-session mark -- be tested without a terminal.
A screen owns its own key loop. What it does with a row (tick it, stop it) is the screen's business; getting the right lines onto the right rows of a window that may be too short is this module's.
#FrameLine
One rendered line and what kind of line it is.
#ListRow
One session in the list, with whatever the screen has decided about it.
selected is the screen's tick state -- None for a screen with no selector column at all, which is how the overview asks for no [x]. state is the per-row state line; state_style names its colour. rss_kib is the resident memory measured for the record's pid, or None when nothing measured it.
#selector
def selector(self) -> str | NoneThe [x] / [ ] toggle, or None for a screen without one.
#row_heights
def row_heights(rows: Sequence[ListRow], *, focus: int, now_ms: int, identity: SessionIdentity | None=None) -> list[int]The block height of each row, which is what the viewport scrolls over.
#move_focus
def move_focus(focus: int, count: int, step: int) -> intMove the focus by step within count rows, clamped at both ends.
Clamping rather than wrapping, matching the form runner's own traversal. An empty list has no focus at all, which is -1.
#build_frame
def build_frame(rows: Sequence[ListRow], *, focus: int, now_ms: int, title: str, hint: str, height: int, width: int, identity: SessionIdentity | None=None, empty_text: str='No sessions.') -> list[FrameLine]Lay the list out into at most height lines of at most width columns.
The window is centered on the focused row and clamped to the content (the viewport's rule), and the screen never draws past its last row. A height too small even for the chrome yields whatever prefix of it fits, so a tiny terminal renders something rather than raising.
A row only partly inside the window is left out, and the lines it would have occupied are drawn blank so the hint keeps its place at the bottom. The alternative -- drawing the lines that fit -- was built alongside this one and compared on a 24-row terminal: at the bottom edge it gains a header naming one more session, but at the top edge it draws a block's indented detail line directly above the next session's header, where it reads as that session's. A line whose owner is off screen is worse than a blank one, and worst on the checklist, where every row is a process someone is deciding whether to stop.
The focused row is the one exception: it is drawn clipped rather than hidden, because a window too short for it would otherwise show nothing at all.
#render_frame
def render_frame(terminal: Terminal, theme: ThemeColors, frame: Sequence[FrameLine], *, left_col: int=2) -> NoneDraw frame over a cleared screen, one line per terminal row.
The two indicator colours are the terminal's own green and red rather than theme entries: "this process is still up" and "this process is gone" mean the same thing under every palette, and a theme that recoloured them would be recolouring the answer.