Skip to content
claudewheel.sessions_table
Edit
On this page

Lays every Claude Code session on the machine out as one framed table of styled spans, with both scrollbars drawn into its borders and no terminal in sight.

#claudewheel.sessions_table

#claudewheel.sessions_table

Lay every Claude Code session on this machine out as one framed table.

This module is the looking, and nothing else: rows, dimensions, a clock, a home directory and two view flags in, a :class:Frame of styled :class:Span\ s out. It opens no file, reads no environment and knows no terminal, so the whole layout -- the column widths, both truncation directions, both scrollbars, the expanded row's detail lines -- is exercised as arithmetic over strings.

What the frame is made of -------------------------

The bottom terminal row belongs to the hint line, which is the screen's business, not this module's; the frame occupies everything above it:

=============== ========================================================== Top border , the column joints, Header the column labels Header rule , the column joints, Data window one line per row, four for the expanded one Bottom border , the column joints, , and the horizontal handle =============== ==========================================================

Two scrollbars, each drawn INTO a border rather than beside it: the vertical one over the right border of the data-window lines, the horizontal one over the interior of the bottom border. Neither costs a column or a line, which is why the table can afford eight columns on an 80-column terminal.

The column strip and the two things that are not in it ------------------------------------------------------

Every cell line -- the header, the joint lines, each row -- is one strip as wide as the columns ask for (:attr:Frame.strip_width), shifted left by hscroll and clipped to the frame's interior. The expanded row's detail lines are deliberately NOT part of that strip: they are full-width prose about one session, and scrolling them sideways with the columns would hide the beginning of a path rather than reveal the end of a column.

Styles, not colours -------------------

A span carries a style NAME (frame, header, row, row_focus, state:<state>, state_focus:<state>, detail, empty) and :mod:claudewheel.sessions_overview is the only place one becomes an escape sequence. The focused row's every span -- including the padding out to the frame's edge -- carries a focus style, so the highlight is a full-width band rather than a coloured word.

#state_style

python
def state_style(state: str, *, focused: bool) -> str

The style name of a State cell in state.

#SessionRow

One session, as the table needs it: already joined, already resolved.

Everything here is a value the gatherer decided -- the state it derived, the profile it attributed a lifecycle-only session to, the memory it measured -- so the layout never has to ask anything about the world. record and lifecycle are carried for the screen's own keys (pruning needs the record, marking needs the session id), not for the layout.

#Span

A run of characters drawn in one style.

#Frame

A laid-out table, and the dimensions the key loop needs back.

hscroll is the CLAMPED horizontal offset, so a screen that asked for more than the strip can offer reads the real one back instead of accumulating a number that stopped meaning anything. window is the data window in lines (what a page key moves by) and total_lines the summed height of every visible row.

#_Spec

One column: its label, how wide it gets, and how it handles overflow.

fixed is a width the content cannot change. Otherwise the column is natural: as wide as its longest cell (never narrower than its own label), floored at minimum and capped at maximum -- and a maximum of None is no cap at all, for a column whose cells must never be cut.

#sort_rows

python
def sort_rows(rows: Sequence[SessionRow]) -> list[SessionRow]

Group rows by state, newest first inside each group.

A row with no start time sorts after every dated row of its group rather than among them, and rows that cannot be told apart by age sort by name, so the order is total and a redraw never reshuffles equals.

#visible_rows

python
def visible_rows(rows: Sequence[SessionRow], show_all: bool) -> list[SessionRow]

The rows shown at this filter setting, in the order given.

The default view hides what is finished -- an exited session and one the user marked done -- because those accumulate without end and are not what the screen is opened to look at.

#tildify

python
def tildify(path: str | None, home: str) -> str

path with home written as ~, or :data:MISSING when absent.

home is a parameter, never read from the environment: this module has no business knowing whose machine it is drawing.

#_truncate

python
def _truncate(text: str, width: int, *, from_left: bool) -> str

text cut to width, with an ellipsis where the cut was made.

#cells

python
def cells(row: SessionRow, *, now_ms: int, home: str) -> tuple[str, ...]

The eight untruncated cell texts of row, in column order.

#_widths

python
def _widths(table: Sequence[Sequence[str]]) -> tuple[int, ...]

The drawn width of each column, given every row's cell texts.

#_pad

python
def _pad(text: str, width: int, spec: _Spec) -> str

text fitted to width: truncated if long, aligned if short.

#_strip_cells

python
def _strip_cells(texts: Sequence[str], widths: Sequence[int]) -> list[str]

Each cell as it is drawn, one space of padding on either side.

#_joint_line

python
def _joint_line(widths: Sequence[int], joint: str) -> str

A horizontal rule with a joint wherever a column separator falls.

#_clip

python
def _clip(pieces: Sequence[Span], start: int, width: int, *, pad: str, pad_style: str) -> Line

The width characters of pieces beginning at start, padded if short.

The one place the horizontal viewport is applied, over a sequence of spans rather than a string, so a row's state cell keeps its own style through the shift and the clip.

#_cut

python
def _cut(line: Line, width: int) -> Line

line with everything past width dropped, for a terminal too narrow.

#_handle

python
def _handle(visible: int, total: int, offset: int) -> tuple[int, int]

Where a scrollbar handle starts and how long it is, in track units.

The handle is the visible fraction of the content, never shorter than one unit (a handle that rounded to nothing would leave a scrollbar with no handle at all), and never past the end of the track.

#_detail_lines

python
def _detail_lines(row: SessionRow, home: str) -> tuple[str, ...]

The three lines under an expanded row.

#_row_line

python
def _row_line(texts: Sequence[str], widths: Sequence[int], *, focused: bool) -> tuple[Span, ...]

One row as three strip pieces: before the State cell, it, and after.

The column separators carry the row's own style rather than the frame's: inside a focused row they are part of the highlighted band, and splitting them out would draw a gap through it.

#labels

python
def labels() -> tuple[str, ...]

The header labels, in column order.

#_clipped_text

python
def _clipped_text(text: str, start: int, width: int, pad: str) -> str

text shifted left by start and fitted to width with pad.

#layout

python
def layout(rows: Sequence[SessionRow], *, focus: int, expanded: int | None, height: int, width: int, hscroll: int, now_ms: int, home: str, show_all: bool) -> Frame

Lay rows out into a frame of height by width characters.

focus and expanded index the VISIBLE rows -- the ones :func:visible_rows keeps at this show_all setting -- which is the same list the screen's keys act on. The vertical window is placed by :func:claudewheel.vertical_viewport.compute_viewport, so a row at an edge is drawn clipped rather than dropped; the horizontal offset is clamped here and reported back on the frame.

Every line comes out exactly width characters long, and a terminal too short even for the frame's own chrome yields the prefix that fits rather than raising.

Search