Interface specification: six workspaces, twenty-seven interface items, styling and keys

This commit is contained in:
Sam
2026-11-10 13:04:05 +11:00
parent ae7fc00e5b
commit b44bf73713
10 changed files with 980 additions and 140 deletions

View File

@@ -1,168 +1,348 @@
# What the interface must show, and three layouts to choose from
# The interface: what we need, how it is grouped, and how it is drawn
Written 2026-11-10. This is the design document the screens are built against. It says
what each page must show, where every fact comes from, what the keys are, and then three
layouts at three different shapes. Nothing here is decoration: each line exists because a
person needs it to make a decision or to know what is happening.
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.
## The four questions the interface answers
Plain English throughout. No invented shorthand. If a word here is not obvious, it is
defined in "Words we use".
1. **What is the state of the work?** todo, in progress, blocked, done — for one project.
2. **What is happening right now?** the run, what the agent changed, what it cost.
3. **What needs me?** blocked tasks, a merge that was refused, a task waiting for a person.
4. **What do I do about it?** reply, unblock, assign, create, merge.
---
Anything that does not answer one of those four is not on a screen.
## Words we use
## Where each fact comes from
- **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.
One owner per fact. The middle column is the only place the interface may read it.
A workspace holds at most **four** panels. More than four cannot be read at a glance on
one screen, so a fifth thing to look at becomes a view inside a panel, or a new workspace.
| What is shown | Read from | Never |
---
## 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 (maximum four) |
|---|---|---|
| task list, status, title, assignee | `GET /api/companies/{id}/issues` | a local cache or folder name |
| blockers ("blocked by AUD-163") | `GET /api/issues/{id}/diagnostics/blockers`, per task | the list endpoint — it carries no blockers |
| thread | `GET /api/issues/{id}/comments` | a copy we keep |
| run state, start and end | `GET /api/issues/{id}/runs` | a log file we write |
| cost | `GET /api/issues/{id}/cost-summary` | our own arithmetic |
| what changed | `git` in `currentExecutionWorkspace.cwd` | a diff we store |
| which folder, machine, session | `<project>/.harness/harness.toml` | Paperclip (it does not know) |
| which task a viewer is on | the same file, `watching` | a hidden second copy |
| **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 rule that follows: **the interface is allowed to be wrong for one refresh, and is
never allowed to be right for the wrong reason.** Every screen shows the time of its last
read, so a stale screen says it is stale.
## Pages and panes
A **page** is a workspace. A **pane** is one program in it. TUIOS keeps part of the
keyboard, so no screen may use `h j k l z n` for its own actions, and a bare Esc must be
free to close a box.
| Page | Pane | Must show | Keys |
|---|---|---|---|
| shell | shell | a prompt in the project folder | — |
| tree | task tree | groups todo / in progress / blocked / done; identifier, status, assignee, title, blocker; a count per group | ↑↓ Tab Shift-Tab move · `a` assign to Builder · `enter` open this task in reply and log · `r` re-read · `q` quit |
| reply | conversation | the thread with author and time; a box to type in; the status that will be sent with the reply | `tab` focus the box · `enter` send · `s` cycle status · `esc` leave the box · `r` re-read |
| log | run log | run state, start and end, then activity events, newest last | `↑↓` scroll · `g`/`G` top/bottom · `r` re-read · `q` quit |
| diff | change | worktree path and branch; commits on the branch; changed files with counts; the diff itself | `↑↓` scroll · `r` re-read |
| new | new task form | title, what it must do, must finish first, proves it is done, assign | `tab`/`↑↓` next field · `enter` next, and create on the last · `esc` cancel |
Two decisions to settle with you, because they change the shape:
- **Is the diff its own pane or the bottom half of the log?** Its own pane is honest (a
long diff never squeezes the log) and costs a workspace. One pane split in two is
tighter and hides the diff while the log scrolls.
- **Does the tree drive the other panes?** My proposal: pressing `enter` on a task writes
`watching = AUD-163` into the project's `harness.toml` and asks TUIOS to show the reply
page. The reply, log and diff panes watch that file and reload when it changes. One
fact, one owner, no message bus.
## What must never happen
- A pane that lies: showing a task as in progress after the run ended.
- A key that does nothing visible, or two keys that do the same thing differently.
- A layout that breaks below some size instead of saying "too small".
- A second copy of anything Paperclip owns, on disk or in memory across refreshes.
- An action with no visible result: every write must show what came back, or the error.
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.
---
# The layouts
## The user interface items
## A — one page each (what exists today, and the safe default)
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.
Every page is its own workspace, one pane, the whole width. Best at 80 columns and on a
phone-sized terminal. Worst for watching two things at once, because switching costs a
keystroke and hides the tree.
### Navigation and framing
```
┌ task tree ── workspace 2 of 5 ────────────────────────────────────────────────┐
│ spike-2026-11-10 · read 11:42:07 · 6 tasks │
│ │
│ TODO (2) │
│ ▸ AUD-166 todo — Add the weekly cost report │
│ AUD-167 todo — Document the merge gate [AUD-166 todo] │
│ IN PROGRESS (1) │
│ AUD-165 in_progress Builder Review gate probe │
│ BLOCKED (1) │
│ AUD-162 blocked — Tree render check [AUD-161 todo] │
│ DONE (2) │
│ AUD-163 done Builder Write NOTES.md │
│ │
│ ↑↓ move · a assign · enter open · r re-read · q quit │
└──────────────────────────────────────────────────────────────────────────────┘
1 shell 2 tree* 3 reply 4 log 5 diff 6 new
```
**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.
## B — cockpit: tree and conversation side by side
**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.
One workspace holds the work; the tree stays visible while you read and reply. The log
and diff live one workspace away, because during a conversation the change matters less
than what the agent said.
**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.
```
┌ tree 34% ─────────────────┐┌ reply ────────────────────────────────────────────┐
│ spike-2026-11-10 ││ AUD-165 in_progress · Builder │
│ read 11:42:07 ││ │
│ ││ agent 11:38 │
│ TODO (2) ││ Workspace Ready — branch AUD-165-… │
│ ▸ AUD-166 Add weekly cost ││ │
│ AUD-167 Document merge ││ you 11:40 │
│ ⤷ waits on AUD-166 ││ Please also guard against HOME being unset. │
│ IN PROGRESS (1) ││ │
│ AUD-165 Review probe ││ agent 11:41 │
│ BLOCKED (1) ││ Done — guard added, tests pass. │
│ AUD-162 Tree render ││ │
│ ⤷ waits on AUD-161 ││ │
│ DONE (2) ││ │
│ AUD-163 NOTES.md ││ ┌───────────────────────────────────────────────┐ │
│ AUD-164 List lines ││ │ > _ │ │
│ ││ └───────────────────────────────────────────────┘ │
│ ↑↓ move · a assign ││ status → in_progress · s status · enter send │
└───────────────────────────┘└───────────────────────────────────────────────────┘
tab focus · esc leave box · r re-read
```
**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.
## C — cockpit with the change always visible
**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.
Tree on the left, conversation top right, diff bottom right. The most information at once,
and the only layout where a person can see a claim and the code that backs it at the same
time. It needs width: below about 120 columns the diff half is unreadable and the layout
must fall back to B or A.
### Lists and rows
```
┌ tree 28% ──────────┐┌ reply 72% ──────────────────────────────────────────────┐
│ TODO (2) ││ AUD-165 in_progress · Builder read 11:42:07 │
│ ▸ AUD-166 cost ││ agent 11:38 Workspace Ready — branch AUD-165-… │
│ AUD-167 docs ││ you 11:40 Please also guard against HOME being unset.│
│ ⤷ AUD-166 ││ agent 11:41 Done — guard added, tests pass. │
│ IN PROGRESS (1) ││ ┌─────────────────────────────────────────────────────┐ │
│ AUD-165 probe ││ │ > _ │ │
│ BLOCKED (1) ││ └─────────────────────────────────────────────────────┘ │
│ AUD-162 tree │├─ diff ──────────────────────────────────────────────────┤
│ ⤷ AUD-161 ││ cleanup.sh · branch AUD-165-… · 1 commit · +4 −0 │
│ DONE (2) ││ @@ -1,2 +1,3 @@ │
│ AUD-163 NOTES.md ││ -rm -rf "$HOME/tmp" || true │
│ ││ +[ -n "$HOME" ] && rm -rf -- "$HOME/tmp" || true │
│ ↑↓ · a · enter ││ ↑↓ scroll · r re-read │
└────────────────────┘└────────────────────────────────────────────────────────┘
```
**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.
### Icons
**27. Glyphs.** A small set used consistently: running, waiting, blocked, done, failed,
unread. Taken from the glyph set TUIOS reports, with an ASCII fallback, and never the only
carrier of meaning. Checked with `tuios list-glyphs` before use, not assumed.
---
# Recommendation
## What each panel shows, and where the facts come from
**C, falling back to B, falling back to A**, chosen by the width the pane actually has, not
by a setting. The person gets the most information their terminal can carry, and never a
layout that pretends to have room it does not.
| 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 |
Concretely:
Two rules follow and are not negotiable:
- 150 columns and wider: C. Tree 28%, right split 55/45 between reply and diff.
- 110–149 columns: B, with the diff one workspace away.
- 80–109 columns: A, one page each.
- Below 80×24: the "too small" screen with the numbers, on every page.
- **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.
Two things to settle before building: whether the tree drives the other panes through
`harness.toml`, and whether the four screens stay four workspaces or become two.
---
## 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 | ? | show the keys for this panel |
| Any panel | r | read again from Paperclip |
| 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.
---
## 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.
---
## Layout, once the panels are settled
The panels above do not fit in one workspace, and should not. Layout is chosen by 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 tree and summary side by side with Needs you under
the tree. Work shows run log and diff side by side. Conversation shows thread above
composer with the task header above both.
- **110 to 149 columns:** two panels per workspace at most; the third becomes a view
switched with a tab inside the panel that shares its kind of content.
- **80 to 109 columns:** one panel per workspace at full width, in the order Board, Work,
Conversation, Author.
- **Below 80 by 24:** one message, nothing else.
The exact three candidate arrangements are drawn in the earlier note and are still valid
for the wide case; what changes here is that the panels themselves are now defined, so the
layout is a matter of which panels share a workspace rather than what a panel contains.
---
## 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.
---
## Decisions needed before building
1. **Does the tree drive the other workspaces?** Proposal: pressing Enter writes the chosen
task's identifier into the project's `.harness/harness.toml` as `watching`, and asks
TUIOS to show the Conversation workspace. The other panels watch that file and reload
when it changes. One fact, one owner, no message bus, and it survives a restart.
2. **Six workspaces, or fewer?** The proposal is six. Fewer means more switching inside a
panel, which costs a key and hides state.
3. **Which glyph set?** Depends on the font in the terminal; must be checked with
`tuios list-glyphs` on the machine the person actually uses.
4. **Is the diff a panel or a view inside the Work workspace?** A panel never squeezes the
run log; a view keeps the workspace count down.