441 lines
22 KiB
Markdown
441 lines
22 KiB
Markdown
# 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.
|