The composer's band is one bar. A live row at its top carries the running
turn's figures and the seat's segments; one body slot below holds exactly one of the field, an
ask, a take's card or the reason it cannot take input. The autocomplete leaves the bar for a
popover, and every ask folds to a single line. Each state below is drawn at 1258px with its
measured height.
The bar
The body is one slot, not a stack. What is drawn there is one thing at a
time, and the live row above it never moves with that choice. The row never wraps: chips that do
not fit collapse behind +N, which lists them.
idle, a quiet seat 46px
Type a message...2
Nothing to report, so the live row does not draw
and the field is the whole bar. The voice switch beside the mic draws when
[tts] enabled is set; a click turns the agent's voice on, a second
click turns it off. The count beside the icon is the room: two other clients are listening
while this one's switch is off, and the title names it.
the voice switch's states 24px
Type a message...2
off (a struck speaker), on (a plain one), a reply
sounding (the mark), and the room when a second client listens. The line beside it is the
switch's own refusal, a send on a dropped socket.
the socket is down, so that was not sent
idle, a live seat 82px
0:42·thinking 1.2k·18.4k↑ 3.1k↓·62% cached·4.8k writtenterminal · a command is runningweb-home-layout · dirtytasks 1 of 3subagents 3 runningprocesses 2· 1.2 GB+7
Type a message...
The row leads with the turn's own figures, every
field the wire carries, in the strip's order. A field the record does not carry is
dropped, never drawn as a zero or a dash.
typing, one line 82px
0:42·thinking 1.2k·18.4k↑ 3.1k↓web-home-layout+9
roll the chip to staging⇧⏎ newline⏎ send
The send joins the row; the keys are a pointer's
detail on the same line and stand down under a finger.
typing, wrapped 104px
0:42·thinking 1.2kweb-home-layout+9
roll the rate-limit chip to staging, but only if the replica caught
the bad statement. If it did not, leave production alone and tell me what it
saw.
The field grows; the live row does not.
An ask: the bar expanding in place
The ask takes the body slot. The words row is the answer's own input, and
the reader's draft is a chip on the live row rather than a second text bar. Every kind
folds to one line - the kind's mark, its subject, the count behind it - and a refusal stays
on the folded bar.
question, one answer 385px
question 1 of 30:42·thinking 1.2k·18.4k↑ 3.1k↓web-home-layoutdraft 2 lines · show+8
Pick the environment to deploy to.
Environments
1Stagingthe pre-production cluster⏎
2Productionlive traffic, and the migration set runs against the replica first
3Development
4Tell the agent something else…
↑↓ move1-9 pick⏎ answeresc reject
One bar, expanded. The question takes the field's
slot and the words row is the answer's own input; the reader's draft sits on the row and
swaps the field in with one click.
question, several answers 311px
question 2 of 30:42·thinking 1.2k+8
Which environments should I roll to?
1Stagingthe pre-production cluster
2Productionlive traffic, replica firstspace
3Development
4Tell the agent something else…
↑↓ movespace toggle⏎ submit 2esc reject
The boxes are the toggled set; the submit key says
how many will go, so a set is never sent unseen.
question, folded to its bar 79px
question 1 of 30:42·thinking 1.2k+8
Pick the environment to deploy to.+2
The reader folded it; one click unfolds. The live
row still says what waits.
every ask folds to its bar 43px each
Pick the environment to deploy to.1 of 3 · +2 behind
Question, permission, held post, document hand-off,
browser hand-off, terminal hand-off, a held vault call and a held configuration write: one
line each, the
kind's mark leading.
held config write, one batch
config write waiting0:42·thinking 1.2k+8
⚠ needs youconfig write
vault.url
currently unset
would become
https://vault.test
vault.email
currently unset
would become
me@vault.test
Write itDon't write
tab between Write it and Don't write⏎ activatesesc reject
A held write shows every key it would change and both
values verbatim, because the reader approves the edit itself rather than a summary of it. A
section's mutually required pair is one card: one save, one approval.
held vault call, locked 386px
vault waiting0:42·thinking 1.2k+8
⚠ needs youvault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
master password2FA code (optional)
ApproveSend back with a noteDecline
tab between the fields and the verbs⏎ activatesesc declines
A locked card cannot name the item - naming it needs
the keys - so it draws the id, the masked diff and an attachment's name, then the masked
master-password field and the optional 2FA code. Approve waits, drawn unavailable, until the
password is entered. The draft carries no secret value; the password rides only the approval.
held vault call, locked · enrolled device 340px
vault waiting0:42·thinking 1.2k+8
⚠ needs youvault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
Unlock with this deviceuse the Vaultwarden password
Send back with a noteDecline
⏎ unlocks with this devicetab to the password, the send-back or the declineesc declines
Where the device holds an enrolment, the gesture is
the card's primary action and Enter runs it; the master password is one link beneath it, so
a broken sensor is a working path away. The approve verb is the gesture itself, and the
unlock material it releases rides only the approval.
held vault call, locked · the gesture refused 340px
vault waiting0:42·thinking 1.2k+8
⚠ needs youvault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
Unlock with this deviceuse the Vaultwarden passwordthe gesture was cancelled
Send back with a noteDecline
⏎ unlocks with this devicetab to the password, the send-back or the declineesc declines
A refused gesture never settles the call: the card
stands, says the platform's own reason under the button, and the password it offers is the
same link as before. Only a release that succeeds sends the approval.
held vault call, locked · the gesture running 340px
vault waiting0:42·thinking 1.2k+8
⚠ needs youvault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
waiting for this device…use the Vaultwarden password
Send back with a noteDecline
⏎ unlocks with this devicetab to the password, the send-back or the declineesc declines
While the OS prompt is up the button says what the
card is waiting for and cannot be pressed twice; the row's other verbs stay as they were.
The state ends when the gesture does.
permission 231px
approval0:42·thinking 1.2kweb-home-layout+8
⚠ needs approvalBash
git push origin polish/rate-limit-chip-softer-34
Tell the agent something else
Allow alwaysAllow onceDeny
tab between the actions⏎ activatesesc reject
The subject is drawn exactly as it arrives; the
approving action leads and the refusal sits last.
permission, with the CLI's reason 251px
approval0:42·thinking 1.2k+9
⚠ needs approvalRead
/tmp/forge-deny-scenario.txt
Path is outside allowed working directories
Reads the contents of a file outside this project.
Allow onceDeny
tab between the actions⏎ activatesesc reject
The subject arrives verbatim and the reason is the
CLI's own line. The approving action leads.
a post held for approval 198px
post waiting0:42·thinking 1.2k+9
Trust Machines·#granite-staging-alertsthread:@alice"the staging deploy is green..."·tags:@bob,@carolReply in Slack
Confirmed by @bob - the replica caught the bad statement, so
production is clear to take the same set.
PostSend back with feedbackDo not post
The words that would go out, with each mention drawn
as the name the core resolved; the approving verb leads, the send-back sits between it and
the refusal. The thread is named by its parent's author and opening words, and the people
the draft tags are their names - the timestamp and the raw ids are gone.
the send-back's words 241px
post waiting0:42·thinking 1.2k+9
Trust Machines·#granite-staging-alerts·tags:@bobPost to Slack
Confirmed by @bob - the replica caught the bad statement, so
production is clear to take the same set.
PostSend back with feedbackDo not post
say staging, not prodSend back
Enter sends, Shift+Enter is a newline, Escape closes;
the verb is unavailable until the field has words. The mic dictates into this box, and so
does the push-to-talk key while the field holds the caret; while a take runs its card
replaces the field, and the card's way out abandons the take. This draft is a root message,
so no thread segment is drawn and the verb says Post to Slack.
a delete held for approval 198px
delete waiting0:42·thinking 1.2k+9
Trust Machines·#granite-staging-alertsmessage:@ved"the replica caught the bad statement..."Delete in Slack
[delete] message 1758901234.482910
DeleteSend back with feedbackKeep it
The same held-draft card, worded for what it would
do: a delete asks to delete, and its refusal is "keep it". The stamp's noun follows the
operation, so the segment names the message it acts on, not a thread.
a document hand-off 214px
document waiting0:42·thinking 1.2k
⚠ needs youdocument hand-off
/tmp/composer-rethink/brief.md
Open reads it in the viewer, where the session asked for comments;
the session waits until you finish. Not now tells it you are not reading this one.
OpenNot now
a browser hand-off 193px
browser waiting0:42·thinking 1.2k+8
⚠ needs youbrowser hand-off · tab 3
confirm the staging deploy page shows the new rate-limit chip
Open browser shows the page the session prepared, exactly as it
stands. The session waits, with no timeout, for your answer.
Open browserDoneNot now
a refusal, on the bar it answers 122px
question 1 of 20:42
Which branch should I land on?
the prompt was already answered
The refusal draws on the folded bar too: a fold
never hides why an answer was rejected.
the draft chip, one click from the ask 82px
question 1 of 30:42·thinking 1.2kdraft 2 lines · show+8
actually, roll staging first and tell me what the replica saw
The field in the body, the ask kept mounted and
hidden behind it, and the ask's chip still on the row to bring it back.
Menus leave the bar
The autocomplete is a popover over the conversation. It was a lane of the
box, and it took the band from 121px to 557px while it was open. The bar's height does not move
with a list.
slash commands 82px + popover
0:42·18.4k↑ 3.1k↓+9
/
Commands 7
▶/compactCompact session context
/modeSet session mode
/modelShow / set session model
↑↓ select⏎ chooseesc close
@ files 82px + popover
0:42+9
@composer
Files 14
client/src/composer/Composer.svelte
client/src/composer/Dock.svelte
client/src/composer/box.svelte.ts
↑↓ select⏎ insertesc close
& subagents 82px + popover
0:42
&cli
Subagents 2
cli-versionsettled 3m
cli-auditnever run
: emoji 82px + popover
0:42
:thu
Emoji 2
:thumbsup:
:thuinking:
/resume 82px + popover
0:42
/resume 0192
Sessions 3
0192e1c0-4a7bthe composer cut
0192e1c0-91fdthe board sweep
the +N, opened 82px + panel
0:42·thinking 1.2ksubagents 3review 3+7
MCPs 3
web-home-layout · dirty
tasks 1 of 3
Type a message...
Every door in one place: the chips the row could
not hold, kept mounted and listed in the row's own order, each opening its own panel.
The chips keep their fixed order and take their
tone in place: the review answer in the accent, a failed process in red. A tone is not a
promotion; the row never reorders under the reader.
Dictation
One canonical take. The take is the body's own row in every phase: a dot
while it records, a spinner while it transcribes, the clock, the level graph or the section bar,
the counts and the way out. There is no second take shape anywhere - no pill on the live row, no
badge - so there is only ever one animation to read.
take, ready 82px
0:42+9
0:00readythe capture is open and nothing is coming through it✕
take, recording 79px
subagents 3 running+8
0:07341 fr1.2 MB48 KB/s✕
take, transcribing 76px
0:42+9
0:072 of 6412 fr✕
take, landed 82px
0:42+9
fix the flaky retry test and
The take goes with its row, the field comes back
with the words at the caret, and the ring takes one green beat.
take, landed truncated 120px
0:42+9
this is what fitted · part of the take is missing
fix the flaky retry test and
The words land and the row says the take was cut,
so the reader knows part of what they said is missing rather than that they stopped
speaking.
take that opened and heard nothing 111px
0:42
nothing above -50 dBFS in 4s · loudest was -38.2 ·
check the microphone is not muted, or held by another app
0:04ready✕
The line names the cause above the take's own row,
and goes the moment a sample lands.
take that failed 111px
dictation failed · try again; restart forge if it repeats
Type a message...
The field comes back with the notice above it, no
recording band and no stop control: the take produced no words, so the reader knows to
re-speak.
The other states
A blocked state replaces the body and always says why. The live row
stands above it; the order the states are read in is the one a reader has to be told.
reading the conversation 53px
Reading the conversation...
The seat's first page is still out. It holds the
body like every other narrative state: nothing can be typed until the page lands, so
the line takes the box rather than sitting above a live field.
a command in flight 53px
0:42
Running /compact
handing over, compacting, waking 53px
handing this session over
not running 77px
not runningthis seat has no session behind it
failed 77px
Input disabled due to errorQuit forge and start it again to recover this session.
a prompt queued, and a cron's 82px + pile
cronnightly sweep: re-run the bench suite2nd
youfix the flaky retry test before the gate1st
0:42
Type a message...
The takeover
The composer is behind the page and stays there. Full screen, top bar,
navigation, the ask and the one text field it already has: the note line. Nothing is added
below the page.
with a hand-off riding the top bar bar 116px
←→browser hand-off · tab 3DoneNot now
confirm the staging deploy page shows the new rate-limit chip
the page the session prepared
page up, no hand-off bar 41px
←→the browser · workback to chat
the page, opened from the live row's browser chip
The two widths
Drawn and checked on the session page's column at 1600 and at 430. Under
pointer: coarse every control a finger reaches takes a 44px target and
the key hints stand down; the row clips rather than wrapping.
Measured in the app at 430 with a coarse pointer:
121px, the row's chips 44px and the field's controls 44px. The figures give way first -
the row clips them rather than the +N - so every chip stays one door away.
question, expanded 273px
question 1 of 20:42·thinking 1.2k+4
Which branch should I land on?
1main
2release branch
3Tell the agent something else…
AnswerReject
recording 74px
0:42
0:07341 fr✕
permission 230px
approval+9
⚠ needs approvalBash
git push origin polish/rate-limit-chip-softer-34
Allow alwaysAllow onceDeny
slash menu 100px
0:42
/
Commands 7
/compact
/mode
blocked 51px
Waking up agent...
takeover bar 166px
←→browser hand-off · tab 3DoneNot now
confirm the staging deploy page shows the new rate-limit chip