forge composer · one bar and its states

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 written terminal · a command is running web-home-layout · dirty tasks 1 of 3 subagents 3 running processes 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.2k web-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 3 0:42 ·thinking 1.2k ·18.4k↑ 3.1k↓ web-home-layout draft 2 lines · show +8
Pick the environment to deploy to.
Environments
1 Stagingthe pre-production cluster ⏎
2 Productionlive traffic, and the migration set runs against the replica first
3 Development
4 Tell the agent something else…
↑↓ move 1-9 pick ⏎ answer esc 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 3 0:42 ·thinking 1.2k +8
Which environments should I roll to?
1 Stagingthe pre-production cluster
2 Productionlive traffic, replica first space
3 Development
4 Tell the agent something else…
↑↓ move space toggle ⏎ submit 2 esc 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 3 0: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
Bash · git push origin polish/rate-limit-chip-softer-34
Trust Machines · #granite-staging-alerts
/tmp/composer-rethink/brief.md
browser hand-off · tab 3
terminal hand-off · terminal 1
orgs.Default.projects.forge.max_workers
read the password for 'bank'

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 waiting 0:42 ·thinking 1.2k +8
⚠ needs you config 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 ⏎ activates esc 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 waiting 0:42 ·thinking 1.2k +8
⚠ needs you vault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
master password 2FA code (optional)
Approve Send back with a noteDecline
tab between the fields and the verbs ⏎ activates esc 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 waiting 0:42 ·thinking 1.2k +8
⚠ needs you vault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
Unlock with this device use the Vaultwarden password
Send back with a note Decline
⏎ unlocks with this device tab to the password, the send-back or the decline esc 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 waiting 0:42 ·thinking 1.2k +8
⚠ needs you vault · vault_update
change the login for 'bank'
id: cipher-id
the change, with every secret value masked
password: ********
Unlock with this device use the Vaultwarden password the gesture was cancelled
Send back with a note Decline
⏎ unlocks with this device tab to the password, the send-back or the decline esc 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 waiting 0:42 ·thinking 1.2k +8
⚠ needs you vault · 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 note Decline
⏎ unlocks with this device tab to the password, the send-back or the decline esc 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
approval 0:42 ·thinking 1.2k web-home-layout +8
⚠ needs approval Bash
git push origin polish/rate-limit-chip-softer-34
Tell the agent something else
Allow alwaysAllow once Deny
tab between the actions ⏎ activates esc 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
approval 0:42 ·thinking 1.2k +9
⚠ needs approval Read
/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 ⏎ activates esc 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 waiting 0:42 ·thinking 1.2k +9
Trust Machines #granite-staging-alerts thread: @alice "the staging deploy is green..." tags: @bob, @carol Reply 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 waiting 0:42 ·thinking 1.2k +9
Trust Machines #granite-staging-alerts tags: @bob Post 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 waiting 0:42 ·thinking 1.2k +9
Trust Machines #granite-staging-alerts message: @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 waiting 0:42 ·thinking 1.2k
⚠ needs you document 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 waiting 0:42 ·thinking 1.2k +8
⚠ needs you browser 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 browserDone Not now
a refusal, on the bar it answers 122px
question 1 of 2 0: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 3 0:42 ·thinking 1.2k draft 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
▶/compact Compact session context
/mode Set session mode
/model Show / set session model
↑↓ select ⏎ choose esc 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 ⏎ insert esc 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.2k subagents 3 review 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.

something wants you 82px
0:42 ·thinking 1.2k subagents 3 processes 1 failed web-home-layout review 3 comments +6
Type a message...

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:00 ready the capture is open and nothing is coming through it ✕
take, recording 79px
subagents 3 running +8
0:07 341 fr1.2 MB 48 KB/s✕
take, transcribing 76px
0:42 +9
0:07 2 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:04 ready✕

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 running this seat has no session behind it
failed 77px
Input disabled due to error Quit 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 3 DoneNot 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 · work back 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.

idle 121px
0:42 ·thinking 1.2k ·18.4k↑ 3.1k↓ ·62% cached ·4.8k written web-home-layout +9
Type a message...

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 2 0:42 ·thinking 1.2k +4
Which branch should I land on?
1 main
2 release branch
3 Tell the agent something else…
AnswerReject
recording 74px
0:42
0:07 341 fr✕
permission 230px
approval +9
⚠ needs approval Bash
git push origin polish/rate-limit-chip-softer-34
Allow alwaysAllow once Deny
slash menu 100px
0:42
/
Commands 7
/compact
/mode
blocked 51px
Waking up agent...
takeover bar 166px
←→ browser hand-off · tab 3 DoneNot now
confirm the staging deploy page shows the new rate-limit chip
the page the session prepared