Files
paperclip-harness/docs/UI-LAYOUT.md

22 KiB
Raw Blame History

The interface: what we need, how it is grouped, and how it is drawn

Written 2026-11-10. This replaces the earlier layout note: it is now the one document that says what the interface contains. Layout comes after this, and is chosen from it.

Plain English throughout. No invented shorthand. If a word here is not obvious, it is defined in "Words we use".


Words we use

  • Workspace — one page in TUIOS. The person switches between workspaces.
  • Panel — one pane inside a workspace. Each panel is a program, or one area of one program, that holds one kind of content.
  • View — a way of looking at a panel, for example "the task tree" or "the diff". A panel can hold several views and show one at a time.
  • Focus — which panel receives the keys. Exactly one panel has focus at a time, and the person can always see which one.
  • Fact — a piece of truth with exactly one owner. Tasks, blockers, comments, runs, costs and worktrees belong to Paperclip.

A workspace holds as many panels as the work in it needs. Four side by side is a good default, because more than four at once stops being readable — but it is not a rule. When a workspace needs a fifth thing, there are three honest choices, and all of them are allowed:

  • a tab inside an existing panel, for another view of the same kind of content;
  • a tool panel that opens beside the others and closes again when it is done;
  • zoom, which is TUIOS's own key, for the moment a panel needs the whole screen.

The only rule is that everything in a workspace belongs to the same question. If a panel does not help with that question, it belongs in another workspace.


How things are grouped

The grouping is by what the person is doing, not by which system the data came from. There are four things a person does with this factory:

  1. Decide and direct. What is the state of the work, what is blocked, what is waiting on me, what do I do next.
  2. Watch. What is an agent doing right now, what has it changed, what has it cost.
  3. Talk. Answer an agent, correct it mid-task, unblock a task with words.
  4. Author and govern. Write a new task or plan, set the verify command, watch the budget, repair the layout when TUIOS loses it.

That gives six workspaces. They are deliberately fewer than the number of things we could show, because a person who cannot find the screen they need has gained nothing from more screens.

Workspace The question it answers Panels (a good default set, not a limit)
Board what is the state of the work, and what needs me Task tree · Task summary · Needs you
Work what is happening now, and what changed Run log · Diff · Worktree facts
Conversation what has been said, and what do I say back Thread · Composer · Task header
Author what am I asking for, and how will it be judged Task form · Guidance · Preview
Project which folder, which machine, what limits, what cost Project facts · Costs · Settings
Repair is the layout sound, and what went wrong Layout map · Health · Log

The shell workspace stays as it is: one pane with a prompt in the project folder, for the moments when the person needs a shell and should not have to leave.

Inside a workspace, panels are either side by side or stacked as tabs, and the person switches between tabs without leaving the workspace. A tab is not a second-class panel: it has the same frame, the same footer and the same focus marker as any other.

Tools that open as panels

Several things the person needs already exist as good terminal programs, and wrapping them would be worse than opening them. Each opens in the current workspace, next to what is already there, and closes when it is done. This is the "many panels can simply be terminal tools" idea from the product statement, done deliberately rather than by accident.

Tool What it is for Opens with
Lazygit branches, commits, staging, history, in the project folder t then choosing it
Hunk reading a pull request or a branch change as a review t then choosing it
shell any command the panel does not cover t then choosing it
git log and diff the task's branch without leaving the interface t then choosing it
ocr the review of the current change, as it runs t then choosing it

Both Lazygit and Hunk are already installed on this machine, and Hunk is a flake input of the system configuration. Neither is ours to rebuild.


The user interface items

These are the pieces the panels are made from. Each one says what it is for, which Charmbracelet component implements it, and what it must show in its empty, loading and failed states. The list is deliberately finite: if an item is not here, a panel does not invent it.

Navigation and framing

1. Workspace strip. A one-line strip at the top of every panel showing which workspace this is and how many there are, with the current one marked. Implemented in Lipgloss; TUIOS switches workspaces, so this is a position indicator, not a control. Empty state: never empty. Failed state: never applicable.

2. Panel frame. A border with a title and a focus marker. Rounded border when the panel has focus, plain border when it does not, plus the word focus in the title so the difference does not depend on colour. Implemented with Lipgloss borders. Every panel has one, and the frame is what the "too small" message replaces.

3. Footer key hints. The keys that work right now, in this panel, in this state. Uses the help component with key.Binding, so a key that does not apply is not shown. Never shows a key that would do nothing.

4. Notice line. One line for the result of an action, such as "sent", "created AUD-166", or the exact error text. Disappears when the next action starts. Uses Lipgloss only; the text is always the server's own words, never a paraphrase.

5. Stale marker. The time of the last successful read, shown in the panel title. The purpose is honesty: a panel that has not read recently says so instead of looking current.

Lists and rows

6. Task row. One line per task: selection cursor, identifier, status, assignee, title, and a blocker badge when something blocks it. The list component with a custom delegate, so filtering and scrolling come for free.

7. Group header. A heading with a count, such as "Blocked (2)". Not selectable. Implemented in the list delegate.

8. Status pill. The task's state as a short word with a distinct style per state, and a different shape as well as colour so it survives a monochrome terminal and colour blindness.

9. Assignee chip. The agent's name, or a dash. Uses the agent name from Paperclip, never an identifier fragment.

10. Blocker badge. waits on AUD-163 on the row, read per task from the blocker diagnosis. When the blocker is finished the badge says ready, because a task that is ready but still marked blocked is the most confusing state we have.

11. Needs-you card. One card per decision waiting on a person: a refused merge, a task waiting for an answer, a task whose blocker finished while it was still marked blocked. Each card says what happened, what is expected, and the one key that acts on it. Implemented as list items with a different delegate.

12. Table. For figures that must be compared: per-task cost, per-run duration, per-file change counts. Uses the table component with fixed column widths taken from the panel width, never from a constant.

Reading long content

13. Thread message. Author, time, and the body with paragraph breaks and code blocks preserved. Bodies are wrapped to the panel, never truncated with an ellipsis in the middle of a sentence. Scrolls inside a viewport.

14. Composer. A multi-line text area for the reply, growing with the text up to a third of the panel, then scrolling. Uses textarea. Enter sends and needs to be stated in the footer, because a text area normally treats Enter as a new line.

15. Diff view. File headers, hunks, and line numbers, with additions and deletions distinguished by a leading character as well as colour. Scrolls inside a viewport. Very large diffs are capped with an explicit line saying how many lines were left out, and a key that shows everything.

16. Run timeline. One row per run: state, what woke it, start and end, duration, cost, and a link to the agent's final message. Uses table or a list delegate.

17. Worktree facts. The folder, the branch, the base it was cut from, and the delivery state. Small, monospaced, never wrapped.

Input and forms

18. Form field. A label, an optional "(required)", a hint line under it, and the value. Single-line fields use textinput; the description uses textarea. Tab moves forward, Shift-Tab moves back, Enter on the last field submits.

19. Validation message. Directly under the field that is wrong, in the field's own words, and the submit key stays available so the person can correct and try again.

20. Result receipt. After a write: the identifier that was created or the state that was set, with the server's response behind it. A failure shows the server's message verbatim, because paraphrasing an error we do not understand is how an afternoon is lost.

21. Confirmation prompt. For anything destructive: the exact thing being removed, and the word that must be typed to continue. Uses textinput with no suggestions.

Status and feedback

22. Spinner with a verb. While a read or a write is in flight: what is happening, not just that something is. Uses spinner.

23. Progress bar. Only for things with a known count, such as "3 of 7 tasks read". Uses progress. Never used as decoration, and never used to fake a duration.

24. Health row. One line per check in the Repair workspace: name, pass, fail or not run, and the reason. Three states, never two, because "not run" is not "pass".

25. Empty state. Every panel says why it is empty: "no tasks in this project", "this task has never run", "no worktree, so nothing to compare". An empty panel with no words is a bug.

26. Error state. The failed thing, the exact message, and the one action that is still possible, such as retry or open the Repair workspace.

Working with several panels

28. Tab strip. For a panel holding more than one view: a single line of named tabs with the current one marked, switched with [ and ] while the panel has focus. Implemented in Lipgloss and styled like the panel frame. A tab name is a word, never a number, so it survives being read aloud.

29. Tool panel. A panel that runs an existing terminal program — Lazygit, Hunk, a shell, a git command, or the running review. It opens on demand in the current workspace, takes focus when it opens, and gives focus back when it closes. It runs the real program, so it is not styled by us and does not pretend to be.

30. Panel closer. Every tool panel says how to close it in its own footer (q or esc), and closing it returns the workspace to exactly the arrangement it had before, with focus where it was.

Icons

27. Glyphs. A small set used consistently: running, waiting, blocked, done, failed, unread. Never the only carrier of meaning.

Measured, then fixed: no Nerd Font was installed and Starship was not either. JetBrains Mono Nerd Font is now declared in /etc/nixos/configuration.nix (so .13, .27 and .51 all get it on rebuild) and Ghostty's font-family points at it. Until that rebuild, TUIOS's default glyph set — ordinary Unicode such as ╭ ╮ ✕ ▎ ● › … — is what renders, with ascii as the fallback. The program therefore uses plain Unicode symbols today and may adopt richer glyphs once the font is present, but never depends on them.


What each panel shows, and where the facts come from

Panel Shows Reads from
Task tree groups, rows, badges company issue list, then the blocker diagnosis per task
Task summary status, assignee, priority, blockers, verify command, cost, last run single task read, blocker diagnosis, cost summary
Needs you decisions and waits task states, merge refusals, pending answers already on the task
Run log run timeline and activity runs, activity
Diff changed files and the change git in the task's own worktree
Worktree facts folder, branch, base, delivery state the task's current execution workspace
Thread messages comments
Composer the text being written, the status to send the person, then a comment or a status change
Task header identifier, title, status, assignee single task read
Task form the fields being written the person, then a created task
Guidance what a good task needs, and the project's verify command the project's .harness file
Preview the description as the agent will receive it the form, not the server
Project facts folder, machine, session, project identifier the project's .harness file
Costs spend by task and by agent cost summary per task, agent budget
Settings verify command, budget, watched task the .harness file, then Paperclip for what it owns
Layout map workspaces and panels as they actually exist TUIOS
Health checks and their outcome the checks themselves
Log what the repair did the repair's own output

Two rules follow and are not negotiable:

  • A panel never invents a fact. If Paperclip does not have it, the panel says it does not have it.
  • A panel shows when it last read. So a wrong panel is visibly stale rather than quietly wrong.

How it is drawn

One theme, semantic roles. Colours are named by role (text, muted, accent, success, warning, error, and a surface colour), and panels ask for the role, never for a hex value. A dark and a light variant exist from the start.

Focus is unmistakable. The focused panel gets a rounded border, a bright border colour, and the word focus in its title. Two of those three do not depend on colour.

Density. One cell of horizontal padding inside a frame, one blank line between sections, no blank line between rows. Rows are single lines except thread messages, which are the only content that gets paragraph space.

Alignment. Identifiers, statuses, assignee names and numbers are aligned into columns whose widths are computed from the content in view, capped at a share of the panel width. Never a hardcoded column width.

Truncation. A truncated title ends in an ellipsis and the full text is available with a key that opens a detail view. Nothing important is truncated silently.

No decoration that carries no meaning. No gradients, no box-drawing around every row, no colour that does not correspond to a state.

Motion. Only two things animate: the spinner while something is in flight, and the selection cursor as it moves. Nothing else moves on its own.

The Charmbracelet pieces we use: list for rows and decisions, table for figures, viewport for anything scrollable, textarea for the composer, textinput for fields with one line, help and key for the footer, spinner and progress for feedback. Tabs are not a component in this collection, so a view switcher inside a panel is built from Lipgloss and the help component, and is styled like the rest of the interface.


Keys

TUIOS keeps h, j, k, l, z and n in window management mode, so no panel may use them. A bare Esc must stay free, because it closes a box.

Where Key Does
Any panel Tab, Shift-Tab move focus to the next or previous panel
Any panel [ and ] previous or next tab, in a panel that has tabs
Any panel t open a tool panel here: shell, Lazygit, Hunk, git, review
Any panel ? show the keys for this panel
Any panel r read again from Paperclip — data only, layout untouched
Any panel R rebuild every workspace and pane from the project file
Any panel q leave this panel's program, returning the pane to its shell
Any panel Esc close the open overlay or leave the composer, never quit
Task tree Up, Down move the selection
Task tree Enter open this task in the Conversation and Work workspaces
Task tree a assign to the configured agent
Task tree Space expand or collapse a group
Thread Up, Down, Page Up, Page Down scroll
Composer Enter send
Composer Shift-Enter new line
Composer s choose the status to send with the reply
Diff Up, Down, Page Up, Page Down scroll
Diff f show the whole file, not the capped view
Forms Tab, Shift-Tab next or previous field
Forms Enter next field, and submit on the last one
Destructive typed word confirm

No key means two things in two panels. Where a panel needs a key that is taken, it uses a different one rather than overloading. Zoom is not ours: z belongs to TUIOS, and the interface relies on it rather than reimplementing it. The same is true of switching workspaces: TUIOS does that, and the interface only shows where you are.


Size, and what happens when there is not enough

Every panel declares a smallest useful size. When a panel is smaller than that, it draws its own short message with the size it has and the size it needs, and nothing else. The program never draws a broken layout and never invents a scrollbar to hide the problem.

Panel Smallest useful size
Task tree 30 columns by 10 rows
Task summary 30 by 8
Needs you 30 by 6
Run log 40 by 10
Diff 40 by 12
Worktree facts 30 by 6
Thread 40 by 12
Composer 40 by 4
Task form 50 by 14
Guidance 30 by 6
Preview 40 by 10
Project facts, Costs, Settings 40 by 8
Layout map, Health, Log 40 by 8

The workspace itself needs 80 columns by 24 rows. Below that, the whole workspace shows one message naming the size it has and the size it needs.

Tabs change the arithmetic in a useful way: two panels tabbed into one cost the space of one panel, so a narrow workspace keeps its content by tabbing rather than by shrinking. That is why tabs are part of the design and not a workaround.


How the panels sit together

Layout follows the width of the terminal, so the person gets as much as their screen can carry and never a layout pretending to have room it does not.

  • 150 columns and wider: Board shows the task tree and task summary side by side, with Needs you tabbed under the tree. Work shows the run log beside the diff. Conversation shows the thread above the composer, with the task header above both.
  • 110 to 149 columns: at most two panels side by side; the third becomes a tab in the panel that holds the same kind of content.
  • 80 to 109 columns: one panel at full width, with the others tabbed beside it, in the order the work happens: Board, Work, Conversation, Author.
  • Below 80 by 24: one message, nothing else.

Two arrangements we already know we want, because they are the moments the person is actually in:

  • Reading a claim and its code. Task tree on the left, thread on the upper right, diff on the lower right. The only arrangement where a claim and the change that backs it are on screen together.
  • Deciding. Needs you on the left with the task it concerns beside it, so the decision card and the evidence are never on different workspaces.

What happens when you pick a task

Plainly: choosing a task changes what the other panels show. Pick a task in the task tree, and the summary, the thread, the run log, the diff and the worktree facts all switch to that task. Nothing else has to be typed, and no screen has to be told twice.

The rest is implementation, and it is written down only so the behaviour can be trusted:

  • the chosen task is remembered in the project's own .harness/harness.toml, as one line naming its identifier;
  • the other panels re-read when that line changes, within about a second;
  • the choice survives a restart or a lost pane, because it is on disk and not in memory;
  • if the chosen task is ever deleted, the panels fall back to the most active task in the project rather than showing an error.

That is the whole mechanism. It is one line in one file, it is not a second copy of anything Paperclip owns, and it can be inspected with a text editor when something looks wrong.

What is not in this version, deliberately

  • No chat with an agent as a separate thing: the Conversation workspace is the chat.
  • No plan editor: a plan is a task's description, and the Author workspace writes it.
  • No second task board: TUIOS has one, Paperclip has one, and this interface is the one that knows about blockers and worktrees.
  • No settings for colours: one theme, two variants, no per-panel colour choices.
  • No dashboards of figures with nothing to act on: every number sits next to the thing it describes.
  • No replacement for Lazygit or Hunk: they open as tool panels.

Decided, and what is still open

Decided by the person, 2026-11-10:

  1. Switching inside a workspace is wanted. Tabs and zoom are part of the design, and a workspace may hold more than four panels when the work in it needs them.
  2. Grouping matters more than panel count. A workspace earns its place by answering one question well.
  3. Picking a task updates the other panels. That is the behaviour described above.
  4. Lazygit and Hunk belong inside the interface, as tool panels, not as things the person leaves for a shell.

Still open:

  1. Glyphs. Measured: TUIOS has ascii, default, heavy and unicode; default is active. Starship is not installed here and no Nerd Font is present. So the interface uses the default set with an ascii fallback unless a Nerd Font is installed on purpose. If Starship's look is wanted, that is a system change first, then a glyph set.
  2. Which panel wins when the terminal is narrow. Proposal: the one that answers the workspace's question, with the rest tabbed rather than hidden.
  3. The order of tabs in each workspace. Proposal: the order in which the work happens — what it is, what it says, what it changed.