A session
The seat’s own page: the conversation in the middle, the projects rail
beside it, and at the foot the surface the queue pile and
the composer share. It is addressed by the seat’s slot -
/session/{org}/{project}/{label} - so a seat is a URL a deep link can
reach; /new and /resume swap the occupant and leave the slot alone.
It is drawn from one read of the session subject - a snapshot, then the
updates that move it - plus the home’s snapshot for the facts that ride a
project’s row and the board’s snapshot for the row’s task, its schedules and
its connector sets. The drawing it is held against is
web-session.html, beside this page.
What it draws
| Region | Shows | Read from |
|---|---|---|
| Rail | every declared project grouped by the strongest state among its rows, the current seat marked, each project’s and seat’s decision waits marked with a glyph and a count, sleepers folded - a fold hiding a decision wait opens itself, so the mark is visible without expanding it; a seat row’s own line names what happened to it - a turn failure, a death with its reason, a refused spawn, or restarting (N of M) while forge brings the seat back - and the footer carries the account and what it is costing | the home’s snapshot, the board’s snapshot for each row’s task, and accounts |
| Header | the chip, the seat’s own mark, org and name, and the facts: session id, model with its effort, mode, context fill, and the compaction count | header, conversation.compaction_count |
| Conversation | the turns and everything inside them, in the column’s own scroll viewport | conversation |
| Queue | the prompts waiting, drawn against what is running | queue, queue_ended |
| Bar | the composer’s one bar: a live row of the seat’s segments and the running turn’s figures, and a body holding the field, an ask, a take or a blocked line | the seat’s record, the project’s board row, the page’s clock, and the composer’s own record |
The chip is the one projects control, and its click is smart. It
counts seats that want a person - a question, a permission, a failed turn,
never a mere change - and exactly one, not an error, goes straight to that
seat; several, or any failure among them, opens the home to pick from;
none goes home too, from the quiet projects word. Hovering it summons
the rail as a peek. It is a real anchor, so keyboard, middle-click and
new-tab all behave. A worker’s row in the rail names the tree it runs
in when a lead pointed it at one of its own: the path alone, a little
under a failure’s sub-row, hung under the worker’s own name so that name
stays whole on its line - and on hover it slides left to show whatever
the rail was too narrow for. Its branch and files reading a tree other
than the project’s is then expected rather than a fault.
The header reads the seat, not the app. The name leads and the org
qualifies it only where the fleet makes the name ambiguous (#1707); the
session id is shown in its slot; effort rides the model it belongs to,
because it is a property of that choice; the context figure is one unit -
how full the conversation is, and how many times it has been cut - and the
compaction count is a control that takes the reader to the latest cut. A
usage nothing has reported draws no track and a dash, never a bar at zero.
Everything the row must fold stays one tap away under more.
The run of rows
Every call, message, thought and notice draws on its own row, in the order it arrived, and the list only appends: no kind gets a row of its own, the kind’s glyph rides the row that draws it, and nothing rearranges under the reader. A message is a tool row and so is a thought - the same rows and marks a run of calls draws - which is what makes traffic and reasoning read as one system.
- A family row carries a tool family’s glyph: read, search, bash,
web, lsp, skill, tool search, config, worktree, tool, and the one
mutation row every edit draws whatever tool it was. An lsp call names
its operation and its subject - the file it ran on
(
hover · /Users/vedhavyas/Projects/forge/client/src/chat/families.ts, with the line it asked about when it named one), or the query a symbol search looked for (workspaceSymbol · renderGroup), because the glyph already says lsp. - A forge call draws a card for its family - tasks, agents, cron,
documents, gotify, handoff, review, slack, terminal, vault, voice - and
the
System One decisions
draw as their own family, so a reader scanning for what the session decided
does not find the three decision calls mixed among its cron and peer calls.
The handoff’s exit call is its own family: its family is handoff, it wears
the handoff mark rather than the workers one, and it carries the note it
handed over. A terminal call is the terminal family’s: it wears the
terminal mark, titles itself by the terminal it acted on (
opened terminal 3,ran a command in a terminal), chips the shell’s state in words (running,waiting for input,exit 2) and quotes the screen or the command’s own text; a parkedterminal__hand_offsays it waits for the person and carries the ask’s reason. A vault call is the vault family’s: it wears the key mark, titles itself by the item it touched where the call names one (created login 'bank',moved item ... to the trash,searched the vault for 'bank'), chips a parked callwaiting for your approvaland a find by its count, draws a search’s rows and a folder list’s names, and draws the one value a reveal asked for - the card replaces the raw body, so a value left out of the card would be a value the reader cannot find. A settings call has no success card, so its open is the payload the tool answered with, unchanged: collapsed it says which way the call went and what it touched -settings · read gotify.url,settings · write gotify.url, client.theme,settings · write 4 keys(one or two keys are named, more are counted) - and a write wearswaiting for your approvalwhile it parks on the dock,declinedif the reader turns it down. A slack call whose answer the card does not dress - the token’s identity, a conversation’s history, a thread, a user search, a transferred file - says what it read (read the history of C1,searched users for 'ved') rather than the family’s own noun, and an attachment carries its direction and its tense:uploading 'brief.pdf'while the dock still holds it,downloaded attachment F1once the fetch answered. - A browser call is the browser’s, whatever server name carried it.
- A peer message draws a card saying which way it went:
sent toandfromname the counterparty and its org,failedcarries the delivery’s own reason, andwhoamiandlistopen onto their facts. - A delivery - a cron fire, a Slack message, a Gotify push, a voice note - is a row of its own kind, a cron fire titled by the schedule that fired it and a voice note by the note’s own first line. A Slack row whose block is late adds its age to the title, so a week-old message reads as history before the row is opened.
- A notice is a tool row, in the run’s own inset. An info, warning or error line - a rate-limit window, an API retry, a turn that failed - draws like a call: a glyph carries the level (a circle, a triangle, a circle with an exclamation, so the shape says it and not only the tone) and the level word is gone, the wire’s own type and a retry’s attempt ride it as chips, and the delay rides a tail. Its words are the message, so they wrap and keep the line breaks they arrived with where a call’s title clamps. A plain notice has nothing behind it, so it is a static row - no chevron, no body, no pointer; a provisional one opens onto its facts and the frame.
- A nudge - a chase warning, a worker’s kick - is the machinery’s own
words, so it draws as a work row under the board’s own mark rather than as
the reader’s: nobody typed it, and it never wears
.mine. A long one - a user-wait nudge - opens on the whole of what it said, the way a thought row does. The row is found by the id the machinery minted for the prompt, which survives into the transcript, so a reloaded row draws what the live one did. - A session start is one row, not one per hook: the firing says what fired and how many hooks it carried, and opens onto them. A hook that finished without a word draws no row of its own inside it; one that failed or is still running stays, and a failed start counts its failed hooks in the closed row’s words as well as taking the failure tone. Every hook still opens onto the whole of what it printed, a start whose hooks all finished silent opens onto a line saying so, and a hook outside a start keeps a row of its own.
- A read draws by what the file is, from the same per-format policy the
Files reader uses: markdown renders as prose, code stays coloured with the
file’s own grammar (a
Justfileisjust, a script’s shebang names its own), and a text file no grammar names draws plain with a caption saying so. - An edit draws its diff the way GitHub does, inside the same panel a read
draws - the panel’s border and ground, with the format on the head when the
file names one: the hunks with both line numbers, the file’s syntax colours on every line,
the row tinted for a removal or an addition, and the changed words of a
paired removal and addition tinted darker. A change the call’s own input
carries as two sides draws the same way without positions: the lines the
two sides share stay as context, and only the lines that differ are
removals and additions - save a change too large to compare, which draws as
one removed block and one added block.
A markdown edit renders
instead: the hunk draws as markdown, in that same panel, with removed
words kept inline, struck and dimmed, and added words drawn normal; the
source diff is not shown under it. A markdown file the call created
renders as the document it is, with nothing marked: the change is the
line-zero hunk a create writes, and the marks line already says the file is
new. A plain row - a command’s output, a
text result - takes the same panel’s border and ground, with no format strip;
a hook run’s output keeps the sheet’s own inset rule.
An edit or a question whose structured record the page could not
carry says so on its marks line: an
omittedfigure with its size, scaled to the unit the size belongs to (omitted, 5.0 MB). On an edit it is one more mark beside the hunk count; on a question, which has no hunk count, it is alone on that line. A record over the cap first keeps the keys the row draws and drops only what nothing reads, so a large edit keeps its patch and a Write draws the file it created from the call’s own input, and a question whose huge typed notes pushed it over keeps itsanswers. A question answered live and the same question read from the transcript draw identically - the cap bounds both paths the same way - so the answer card is the card either way. Only a record with nothing drawable left falls back to the omission: an edit names it on the marks line, and a question draws as its own row, its question glyph and the question it asked, opening on the omission. - A backgrounded call is held open by its task fact, its result
saying the command started, and the ending landing later as a frame of
its own. Its output box follows a call that is still out: while the
row stays open and the call runs, the row re-reads the command’s own
output file on a one-second tick, and the tail it draws carries a
livemark saying the box is still being written. A read that found nothing while the call runs says the output has not appeared yet: the file is there from launch and reads as empty until the command’s first line lands, so the settled wording about a call that never recorded one would be a claim the row cannot make yet. Once the call settles the mark goes and the tail is the finished output. A call theTaskStoptool stopped has no ending frame of its own: the tool path sends no task frame and no registry change, so its own success answer settles the call - in whichever held turn the call sits, which is not always the turn the stop lands in - the row statesstoppedbeside its title, and the processes row stops counting it. A stop whose call the page does not hold draws as a row of its own, named with the task it stopped. The box draws the command’s own colours: every terminal box reads the escape sequences a command wrote - colour, bold, dim, italic, underline - into the theme’s palette, so a coloured compile looks like the terminal’s. A sequence the page cannot draw (a cursor move, an erase, the extended colour forms) is dropped rather than printed, and a command that wrote no colour draws in the box’s own tone. - A message’s image draws as the file row a turn attached does: the picture named, and its source printed beside it. The page fetches nothing for it - a message is text someone else wrote, so the host it names is never asked for.
- A settled failure with nothing to open onto says so in words rather than drawing an empty body.
- A frame the page has no face for yet draws as itself, marked
provisional. An unknown top-level frame and an unknown content block, and
the CLI’s own system lines for an API error, an informational line and a
notification, each draw as a notice row whose level is its glyph and whose
chip is the wire’s own type. The open body lists what the frame carried as
one-line labelled values - an error’s message, status and retry, an
unknown frame’s fields - and the frame itself sits behind a second control
under them, so the payload stays reachable while the row’s face is the
facts. The frame is seen the day it arrives and can be given a real face
later, rather than being dropped. A permission refusal draws on the call
row it refused, which no result frame will ever settle: the row’s chip is
the wire’s own
permission_deniedand its body carries the CLI’s reason where the result said nothing, so one refusal is one row. A refused mutation keeps its diff and the reason rides under it, because the diff comes from the call’s own input rather than from the result. It draws as the notice above only where the fold holds no call row for it - a page opened after the call, or a peer message drawn as a card, the one card a call never also draws a row for - and the refusal is never dropped. A sub-agent’s refusal draws inside the dispatch that owns it: the frame names the agent rather than the dispatch, and the server walks the agent’s dispatch chain up to the top-level card - a second level’s call is a child line of the card and a third level’s names that child line, so an agent two or three levels down still resolves to the expansion that draws it. The refusal rides that expansion’s call row with the same chip and body, or draws as a notice line there - the level glyph and the wire’s own chip the session’s notice carries - when the expansion holds no call row for it. A refusal no instance owns - a task the roster never linked - stays at the session’s level. A resumed page draws a sub-agent’s refusal from its own sidecar, as the call’s errored result, and a nested sidecar’s frames fold under the expansion that owns them there too: the fold resolves the chain from the sidecar metas, so a parent’s body past the resume budget does not strand its child, a sidecar whose own body is past the budget is skipped, and a sidecar whose chain reaches no dispatch the transcript answers joins nothing. A question draws nothing here while it waits: the dock is its row. It draws as a card instead - the words the reader was asked, markedunanswered, with nothing picked or typed under them - the moment its own result lands with no answer on record (the reader dismissing the dock), or the turn closes on it. A question whose result came back an error, or one the CLI refused before it ran, keeps its call row instead. - A model swap, a memory recall, a local command and a project’s
development intent each draw a row of their own. A model fallback (and a
refusal retry, a refusal with no fallback, and the Fable consent gate) draws
a warning whose words are the CLI’s own
content- or, for the refusal retry, which carries none, a line saying the session changed model - and whose open body names the trigger, the model it left and the model it fell to. A memory recall draws one line saying how many memories the turn was given, with the paths and, where the CLI supplies it, the recalled body in the open body rather than one row per memory. A local command draws one row: the command it ran, with the command’s output in its open body. The CLI sends the name and the print as one frame (2.1.280) or two (older), and a print fills the row the name opened - so a command is one row and not two, and there is no assistant message carrying the output, because forge reads the raw system frames. A project development-intent frame draws one quiet line naming the platform the CLI saw, with the evidence in its open body; the CLI’s emit cadence is not captured, so the line is a fact about the project, not a count of turns. - The CLI’s own repository events belong to the working-tree chip, not
the conversation. A
vcs_state_changedframe is the tree’s own news: the tree’s chip on the composer’s live row says what moved for a moment - the kind and, where the CLI attributed it, the branch - and the tree row re-reads on the frame rather than at its next staleness window, so no chat row is drawn. Acode_change_publishedframe keeps a chat row only for the four transitions a reader would otherwise miss -created,merged,closedandreopened- naming the action, the repository and the change number, with the URL a reader opens as the quiet tail; every action rides the chip’s transient line, and the PR row carries its state after the core’s own lookup, so an edit, a comment or a checkout is not silent - and the four that keep a chat row ride both. The live row is the shown seat’s: a mutation on a seat you are not looking at is not drawn, its frame belonging to the seat but the line to the one on screen. A chat row’s open lists the frame’s own fields, so a fact the line does not spell is still reachable. Both are events from the live window: the CLI writes neither to the transcript, so a conversation reopened from it shows the tree and the PR row, but not the transient line.
The conversation is cut by the server into turns, and the client folds each turn’s frames into its rows; a compaction boundary draws where it happened, collapsed, with the header’s count saying how many and the mark saying where. A settled turn ends on its turn row, and a failed turn on the failure line just under it; a message that arrives at the turn’s end, a peer message among them, draws below either, where the next turn starts.
A handoff opens a new chat. When forge replaces a session with a fresh
one that continues from a brief - at a turn boundary past 85% of its window
by default, after a compaction, or when one is asked for - the successor’s
transcript opens with the brief forge delivered, and that prompt draws as
the handoff procedure: the row wears the handoff’s own mark, names itself,
and holds the whole brief behind its open. The brief is the ground truth
forge read off the transcript, git and the board - the seat, the working
directory, the branch, the role, the files read and changed, the rows the
seat owns and their links, the transcript the record names, and what was
still running when the seat changed: the live agents, background tasks,
monitors and the session’s process tree, so the successor knows what to
resume - then the note the handing-over session wrote, or its closing text
under a source label when the exit call carried none: where things stand,
what is in flight with the id that names it, what failed, and the exact next
action with the file, command or row it starts at, so the successor opens
what the note names rather than reading a retelling of it.
The previous conversation goes with the seat that left. The chat clears on the swap rather than carrying the old turns above a seam: the successor is a new session with its own transcript, and what it draws is its own. The handoff record keeps the old session’s id.
A row is drawn before the list has measured it. The virtualised list places a row on an estimate-derived offset and corrects it the moment the row reports its own height; drawn rather than left blank, so a fast scroll through fresh history reads as moving content instead of a black column. The correction is the list’s, and the column’s foot and parked anchor absorb it, so the place the reader holds does not move under the reader.
A conversation with a saved place opens at it; everything else opens at the latest. A place is the row the reader held and how far into it they were, kept per conversation - the seat plus the claude session id it runs as
- in memory for the app run. Any page that unmounts the session, such as a
document, the review, the files, the home or the settings, leaves the
connection keeping the conversation, so the mount that comes back puts the
reader on their row; switching away to another seat and back does the same,
and so does a seat reopened on a new connection. A new connection’s first
page holds the newest turns, so it can start below the reader’s row; the
restore asks for the pages above it until the list reaches the row, and a
page that never carries it inside the restore’s budget of about a second
leaves the reader at the latest with the place kept for the next visit. A
place the conversation no longer holds - its row taken by a compaction or a
trim, with nothing left to load - goes to the latest rather than clamping to
whatever row is left. A replacement that names the SAME claude session id is
that conversation reloading, so the place survives it - unless the place was
recorded before any id was known, which goes with the first id announced. A
place the reader gave up, by a send from this client while the column is
drawn or by scrolling back to the latest, is not restored; a
/newor a/resumeto a different claude session id is a different conversation and opens at its latest (#1673, #2834).
A parked column is frozen. The moment the reader scrolls up, the row
under their eye holds its exact offset - sub-pixel included, so a reflow
above does not move the words they are reading - while the scrollbar grows
with what lands below. Coming back within the 48px notch of the bottom on a
hand or a gesture aiming at the foot follows again; a layout move that
happens to land inside the notch counts as standing at the foot and re-arms
nothing. The circle in the corner is the instant jump there, and it is a
44px target on every client, finger or pointer. The
circle counts what was missed: the turns appended after the one the reader
parked at ride a small badge that reads 9+ from nine, and following clears
it. Sending from this client while the column is drawn - the composer, a
takeover answer, the palette - is the reader asking for the latest, so it
goes there and follows; a message the reader sends from another client lands
where they are and joins the count, and a nudge from a page that does not
draw the column, such as filing a review or answering a document, leaves
their place to reopen.
A following column glides to the foot. Every append - a row, a card,
the reader’s own message - lands the measured bottom over a short ease of
about 160ms, a streamed line and a row that measures taller included, with
the glide repeated until the content settles. A first pin, a switch’s or a
return’s, lands at once, and so does an entry’s settle until the list has
measured the rows already in the element. The circle’s jump lands at once as
well. prefers-reduced-motion: reduce makes every landing instant.
The column’s top edge says when it is fetching. An older page is asked for as the reader nears the top and answered a moment later; while it is in flight the wheel has nothing left to scroll, so a small pill at the top edge says the older messages are loading rather than leaving the column reading as a dead scroll. The reader’s own page is sized by what the column has measured - about a screen and a half of its own turns where the turns allow, one turn at minimum - while a walk toward a distant row keeps the full page. A conversation that fits the room has no top to scroll to, so it asks for the page above on the fitting state itself and keeps asking as pages land, until the history fills the room or ends.
The held history settles on the newest window. Reading back through a
seat keeps whatever the reader pulls, and the server answers every page below
its own window from the transcript under the same [server] more_page_cap_kb
byte budget as any other more page, so a page is never a whole transcript
read. One turn over the budget still rides whole, so a long walk can still
hold more than 8 MiB: when the reader comes back to the latest, the oldest
pages are dropped until the held set fits 8 MiB, and a seat the reader has
left settles on each page that lands while it is away. Dropping is by whole
page and never under the row at the column’s top edge: a page’s rows share
the cursor that re-fetches it, so a row cut from inside one would be skipped.
The newest page
and the subscribe seed stay whole even when they alone are over the window -
their rows name the place above themselves, and no more can return them.
The first page left carries the cursor that re-fetches it - the one its page
was asked with, or the newest page’s own handle when the trim kept that page
whole - so scrolling back into the trimmed stretch asks for it again and the
column’s top-edge pill says it is loading. Nothing is made unreachable.
The live row
One row of segments at the top of the composer’s card, in a fixed order: the browser, the terminal, the agents, the processes, the connectors, the schedules, the MCP servers, the files, the tree, the review, the tasks, the monitors. Each segment draws what it has and nothing else, and the row stands whenever anything has something to say - a running turn is one of the things it may say, not the condition for it, because these chips are the home of what the inspector used to hold. The row is in the composer’s own card, not a lane above it: the composer page draws its shape and what the body below it holds, and its drawing shows every state.
The browser segment opens the surface, not a window (see browser.md): its panel lists the tabs of the profile this seat’s project resolves to, show raises one of them as the full-window takeover, hide lowers it, and the sessions keep driving the same tabs underneath while it is closed. It draws only while that profile has a tab, so a page that has never used the browser carries no browser row.
The terminal segment raises the seat’s terminal (see terminal.md): show opens the full-window surface, hide lowers it, and the row draws for every seat - on one no agent has opened, its button is start, the person’s own door; a seat whose read is refused draws the note that says why instead.
The files slip is the one mandatory segment (Ved, 2026-10-17): every
seat has a tree - a worker’s own worktree, the lead’s checkout - so it
draws for every seat with a record, where the tree row beside it keeps its
rule of staying away when nothing has moved. That rule does not silence
the segment: a repository event with no tree row to ride - a commit on a
clean default branch - draws a note-only line for the event’s eight-second
window, a status rather than a control, and the segment is gone once it
clears. The slip IS the door, with no panel: a press opens the page
that reads the whole tree
(/session/<org>/<project>/<label>/files), reading walk · files for a
seat in a worktree of its own (the worktree’s name, so the destination is
never a guess) and plain files for the project’s checkout, with the
browser’s own mark beside it and no count. No hover dialog, no panel -
there is nothing here but the way in.
The processes row is the seat’s running calls. A call the model backgrounded and a long foreground one the CLI is tracking while it runs both draw, led by the CLI’s own description and joined to the OS walk for the memory and pid its command names. A foreground call settles with the turn that ran it; a backgrounded one outlives its turn and leaves when the CLI’s own registry stops naming it.
Every cell is a read, not a computation. The schedules row keeps the
seat’s own set - a cron names the seat that created it - and its
countdowns re-derive against the page’s own clock, as the monitors’ ages
do; the tasks row reads the project’s slice of the board; the tree row is
the seat’s own working tree, the worktree for a worker and the project’s
directory for a lead, and a worker a lead pointed at a tree of its own is
named by that tree - ~/Projects/nazar beside its project’s siblings -
rather than described. A tree that could not be read says so rather than
drawing a blank. The account a row chips is on the rail’s footer
beside what it is costing, not on the live row: a spend-billed key shows its day,
week, month, balance and cap, and a window-billed account shows its 5h and 7d
rows, each with its percent, the reset that lifts it, and at limit on the
window spending the plan.
The review row’s panel is the review sets. The filed rounds draw oldest first with the latest at the bottom - the conversation’s own direction - each a summary line and a tally of its threads’ states; under it a thread collapses to its file, its line and its toned state, and a click opens the commented code (the branch diff around the line the server resolved the anchor to, or the lines captured with the comment where it has moved on) beside the whole thread, its turns in order.
The states the page can be in
- A seat with a conversation, which is the ordinary case.
- Waking: a seat nothing has started. Opening a lead’s page starts it,
and a worker’s seat with nothing behind it draws
not runningand the reason, because a worker is spawned by the lead that owns it. A seat this client just closed is never asked: the roster landing it asleep is that close arriving, not a wake to start. - A seat whose session died: the row carries the death with the reason it left, drawn with the failure mark, until the seat is shown - even after the seat is back up, when its lifecycle reads what it is now. The trace is spent by the seat being SHOWN, or by a prompt the reader themself sent; a subscription alone does not spend it, and neither does a cron’s, a peer’s or forge’s own prompt. An opened lead’s page starts a died seat again the way a sleeping one is. What stays parked: a Failed row with no death behind it, which is a spawn that never came up - the reason line says which - and a seat waiting on sign-in, whose lifecycle is its own and is never woken. The recovery that brings a died seat back says so in the chat - the session resumed, or a new one under a new id.
- A refused read: the server turned the subscription down, and its own words are drawn.
- A connection that dropped: the page keeps what it last read, and the rail’s footer says so where the build facts live.
- A forge on another protocol: the same footer line names the skew; the page behind it is live, and nothing is refused over a version.
- An account the poller has not reported: a window-billed account keeps
its 5h and 7d rows with
-for a window it has no reading for, and a project whose account chip has not resolved draws the account it declares - oraccount- withusage not reported. The footer never vanishes, so an absent snapshot cannot read as forge having no account. - An account at its limit: the footer still names it and states both
windows, because a rate-limited response is a reading rather than an
absence. The account line says
rate limitedbeside the name - the dot’s colour alone cannot, and neither can the loading state, which reads ready (or bailed at boot) while the rotation holds the account out. The spent window saysat limitbeside its percent, in words as well as the amber, and each window states its own reset in the reader’s clock -resets 23:10, orresets Tue 09:00when it is more than a day out; a reset the clock has passed states nothing, and its window stops claiming to be spent. A window the provider rejects below its cap is marked too: the count can be a request limit, not only a token one. The reading lives in memory only, so a relaunch falls back to the last successful probe - or-where there is none - until the account answers again.
The band is where every narrative state speaks. A seat coming up, the
first page still out, a command in flight, a compaction, a handoff and a
worker’s not running all draw one line in the composer’s band, with the same
sweep and only the text changing; the column draws none of them, and the
composer’s own page describes that draw. The column keeps one thing for the
read wait: a send made before the page arrived is drawn there as the reader’s
own row, so words already on their way are never invisible.
The two widths
The page is checked at 1600 and at 430, against the drawing beside
it. The rail is a column by default, remembered per device; the hover mode,
or a window below the width a column stops making sense at, gives the chip
instead: hovering summons the peek, Cmd+Left or the palette opens it to
stay, and Escape or Back closes it. At 430 the chat keeps its
single column,
the facts row folds under more, and nothing overflows horizontally at
either width.
Touch is the rule, not a fallback, and this page is where it is held: the client runs on Android, so every control a finger reaches takes a 44px target under a coarse pointer, and the rail’s close chip is the one affordance hover uncovers - a touch screen keeps it shown and a keyboard focus reveals it, so it is reachable either way.
The system bars are the page’s own edges. The Android window draws
edge-to-edge, so the shell measures the status bar and the gesture bar and
writes the four lengths as --safe-*; the header row, the chat’s foot and
the summoned rail are each padded by them. A tap in the status bar’s band
goes to the system before the page sees it, so the chip and the palette’s
magnifier - the touch door to the rail - have to sit clear of it.