Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/changes/unreleased/946-question-tabs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
kind: changed
title: questions from one step are one panel with tabs, and permissions from one batch one frame
pr: 946
surface: [chat, docs]
invalidates:
- "Two or more questions from one step were a SHEET (internal/tui3/questionsheet.go): a
list grouped by shape, answered row by row, sent with `s`, with `g` spreading one answer
over every row alike. The sheet, `s`, `g` and formsSheet are deleted. Those questions are
now ONE panel with a tab per question in its top edge and a review tab last
(internal/tui3/questionset.go): `←→` move between questions, `↑↓` still choose, `enter`
or a digit HOLDS the answer and moves on, and the review sends every held answer through
answerQuestions in tab order, in one command. A question nobody answered stays open."
- "What counted as 'the same step' was the SURFACE's guess: questionStep, a three-second
gather (questionGatherMsg) and questionReach.pending held a quiet question back until a
step boundary. All of it is deleted. The one key is the engine's step token,
session.Question.Batch (#910); nothing is held back, and a question is pinned the moment
it arrives."
- "Several permissions from one tool batch each got a frame of their own, one after
another. Two or more with the same Batch, each with a plain grant and a safe answer, are
now ONE frame: what each call wants, then `allow all N · one by one · deny all`, with the
pointer on `deny all` where each of them asked alone would open on `deny`. `one by one`
opens them as tabs. The frame carries no `how long` row, for the reason a single
permission carries none (#953). An irreversible or
confirmation question never joins a set or a frame."
- "docs/design/questions/DESIGN.md said questions were BATCHED AT THE BOUNDARY. It now says
ONE STEP, ONE PANEL, and the gallery's 'Several at once — the sheet' screens are replaced
by the tabs screens, with a 'Several approvals at once — one frame' section beside them."
---

The step was always the engine's to know: a model that calls three tools at once raises
three questions in one step, and only the engine saw the step begin and end. The surface's
own copy of it guessed with a clock, and while it held a question back the turn waiting on
that question could not move. Keying the set on `Question.Batch` removes the guess and the
clock together, and the review sending through the one door keeps F's law that one gesture
is one command, in order.
8 changes: 8 additions & 0 deletions docs/design/icons/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ than pictographs, so they are geometry and neither tier swaps the byte.
| it no longer needs answering | `GWithdrawn` | `⊘` | nf-fa-ban | `-` |
| the answer your enter takes | `GPointer` | `▸` | *(geometry — same byte)* | *(the question draws `>`)* |
| the asker's pick | `GRecommended` | `◆` | nf-fa-star_o | `*` |
| the question of several the panel is showing | `GTabHere` | `●` | nf-fa-circle | `@` |
| a question of several still waiting for its answer | `GTabOpen` | `○` | nf-fa-circle_o | `o` |

The two tab slots are for several questions one step raised, drawn as one
panel with a tab each (`internal/tui3/questionset.go`): `●` is the tab the panel
is showing, `○` one still waiting, and a question already answered in the set
wears `GSettled`. They share their plain bytes with the plan's step dots, and the
test that allows that says why (`TestOneGlyphOneMeaning`).

And the ONE frame a question hangs in (`internal/tui3/frame.go`, owner ruling
2026-09-11): `GFrameTopLeft` `╭`, `GFrameTopRight` `╮`, `GFrameBottomLeft` `╰`,
Expand Down
6 changes: 3 additions & 3 deletions docs/design/questions/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Before the engine puts a question to the person, the asker has climbed:
The engine's question gate refuses an `ask` with no reason, no stakes, or fewer
than two answers where the kind needs them, and tells the model to decide or
state the assumption. At most `QuestionCap` open questions per task; past it the
model must consolidate into one sheet.
model must consolidate them into fewer questions.

## The object — `session.Question`

Expand Down Expand Up @@ -183,7 +183,7 @@ never resolves a task by itself.
- **pointer** — every question with answers has the person's pointer (`▸` on a card, the band on a line): `↑↓`/`←→`/`tab` walk it, `enter` takes it, a digit takes at once. It starts on the asker's pick, which says `suggested` in its consequence column; on a confirmation it starts on the answer that loses nothing. The task record page (enter on a needs-you row) draws the node's landing question above its foot and takes its keys through the block's door. Landed 2026-09-10.
- **room** — a page over the conversation (the task-room idiom), and the owner's page pick A of 2026-09-11: **two panes with a seam between them** where the body is `railSlimFloor` cells or wider — the answers on the left with the question's own `Attach` under them as ONE dim-titled section (`what it showed you`), and the evidence of the answer the pointer is on beside them — and **one column** narrower than that, the same evidence unfolded under that answer's own row. Nothing folds: the answer the pointer is on IS the open one. `x` compare on the asker's dimensions (fallback: `+`/`−` lines), `c` comment on the focused part, `?` ask back (one exchange per option, answered in place, closes with the question), the foot composes the answer (pick · with · notes · scope), `d` you decide shows the pick and reason first, `D` sets the dial for the kind. **The pointer walks it**: `↑↓` choose (the foot says so) and the answer it is on is always in view, `enter` takes it, a digit walks to it rather than answering, `→ detail` hands the arrows to the evidence pane and `← back to the answers` hands them back; a click on an answer's ROW moves onto it, and a second click on that row is `enter` — one press to read, one to decide. **The page keeps its own key road** — the two tiers written into its foot's rule, in place of the legend.
- **panel split** — the same reading on the block above the box (preview picks A and B, 2026-09-11): where the ANSWERS carry blocks, a panel `railSlimFloor` cells or wider draws the list on the left and the pointer's own evidence on the right, and a narrower one unfolds that evidence under the pointer's row — the row giving up its consequence to it rather than saying it twice. A panel never takes more than half the frame, and what will not fit is cut on a dim row saying how much is left and `o open full`. **A sentence about the whole QUESTION crosses the seam**, at the frame's own width, inside the frame — the clock's `any key stops the clock · you can still change the answer afterwards` (#954) is the worked example: in a column half the panel wide it is cut mid-word, in the pane beside the list it reads as the pointer's own answer, and under the frame it would sit in the second key tier. On the page the same sentence stands with the reason, above the panes. One account of the case and one block renderer serve both, and the page (`internal/tui3/questionevidence.go`).
- **sheet** — many questions from one step or many hands: grouped by shape, `✓`/`?` per row, `enter` opens one, `s` sends what is answered and delegates the rest, "same answer for all like this" on a group; dependent questions withdraw with a reason and the sheet re-flows.
- **tabs** (owner pick `multi — A`, 2026-09-11; `internal/tui3/questionset.go`) — two or more open questions sharing `Question.Batch` (the engine's step) are ONE panel: a tab per question in the top edge (`●` on screen, `○` waiting, `✓` answered) and a review tab last. `←→` move between questions, every other key is the question's own; `enter`/a digit HOLDS the answer and moves on; the review sends every held answer in tab order through the one door in one command, and a question nobody answered stays open. `esc` folds the whole set. Where every member is a permission with a plain grant and a safe answer the set is ONE permission frame — what each call wants, then `allow all N · one by one · deny all` — and `one by one` opens it as tabs. It carries NO lifetime row: a consent's answer scope never reaches the gate, so the frame offers what it can honour and nothing else (#953 grades the calls and brings the row back). Irreversible and confirmation questions never join. It replaced the **sheet** (a list gathered at a surface-side "step boundary", `s` send / `g` same answer), deleted with its gather.

**An answer's evidence reads in three tiers**, wherever it is drawn: the label bold in ordinary ink (as on a card — the hue is on the marks alone since the 2026-09-11 colour ruling below), the body ink, and every aside dim behind its own word — `then ·` the consequence, `why this one ·` the pick's reason, `would switch if` (the asker's sentence joined onto the clause, never `if If`), `confidence ·` how sure it is — then the blocks, a blank row above each.

Expand All @@ -198,7 +198,7 @@ conversation with the same answers row.
- **THE SETTLE GUARD.** A question accepts no key pressed before it had been on screen for `questionSettle` (250ms); a key that arrived earlier is dropped, never applied. Destructive answers never share the routine key; the confirmation kind keeps stop.go's and tabclose.go's laws verbatim.
- **THE BOX IS NEVER MOVED UNDER A HAND.** A question arriving while the box holds words queues behind the chip until send or a pause of `questionQuiet` (3s).
- **PRESENCE-AWARE DELIVERY (D2 = A).** On the page: pinned now. On home or another page: in the row, plus the chip. Away (`awayAfter`, 10m without a key): dial resolves what it may and records `DecidedBy: dial`; the rest go to home and the phone; the terminal bell rings once for a blocking question only (D6 = A).
- **BATCHED AT THE BOUNDARY.** Non-blocking questions raised inside one step arrive together as a sheet at the step's end; a blocking one arrives at once with the count of what is behind it.
- **ONE STEP, ONE PANEL.** Questions raised by one step of a turn (`Question.Batch`) are drawn and answered together as tabs, and nothing is held back to make that happen: every question is pinned the moment it arrives. (This was BATCHED AT THE BOUNDARY — non-blocking questions held until the model spoke again — which the engine's step replaced; the hold once deadlocked two `ask` calls behind a boundary that needed their answers.)
- **WITHDRAWN, WITH A REASON.** A question whose subject is gone, whose plan changed, or that another answer made moot is withdrawn by the asker; the surface says `⊘ <head> — no longer needed · <reason>` once, dim, and the chip count drops.
- **THE ANSWER IS THE RECORD.** A dim line stays where the question was: `decided <head> → <picked> · with: … · you · 14:02 · reversible · c change`; `c change` shows the unwind cost before it reopens; irreversible records say `cannot change`. A record is read by the model before it asks anything.
- **RULES ARE OFFERED, VISIBLE, FORGETTABLE (D4 = A).** The third same-shaped yes offers `r make it a rule for <scope>`; a row answered by a rule says `· your rule from <day> · change`. Never a hidden rule.
Expand Down
46 changes: 34 additions & 12 deletions docs/design/questions/GALLERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,22 +221,44 @@ for it) and a sentence about assumptions — the row borrows the task proposal's
![one struck](screens/assumptions-stand-until-one-is-struck-struck.png)
*`2`.*

## Several at once — the sheet
## Several at once — tabs

![two questions raised together](screens/two-questions-in-one-step-arrive-as-a-sheet-raised.png)
*Two `ask` calls in one step, grouped by shape, each row carrying that question's own
keys. `▸` is the cursor, `?` is a row still waiting.*
![two questions raised together](screens/two-questions-in-one-step-are-one-panel-with-tabs-raised.png)
*Two `ask` calls in one step are one panel. The top edge is a tab per question — each
called by the words that tell it apart, the words both heads share taken off — and the
review last; the bottom edge adds `←→ question`.*

![one answered](screens/two-questions-in-one-step-arrive-as-a-sheet-answered.png)
*`1`. The row wears `✓` and the answer beside it, the cursor walks on, and `[g] same
answer for all like this` is offered now that there is an answer to spread.*
![the review, nothing held](screens/two-questions-in-one-step-are-one-panel-with-tabs-review-empty.png)
*`→ →`. The review names each question with `not answered — ← to go back`, and with
nothing held it offers no send at all — `enter` is off the edge.*

![spread](screens/two-questions-in-one-step-arrive-as-a-sheet-spread.png)
*`g`. Both rows answered, and `[s] send what is answered (2)`.*
![the review, both held](screens/two-questions-in-one-step-are-one-panel-with-tabs-review-held.png)
*`← ← 1 1`. `enter` (or a digit) on a tab holds the answer and moves on; nothing has
gone to the engine yet. The review lists what is held and offers `send all 2`.*

![sent](screens/two-questions-in-one-step-arrive-as-a-sheet-sent.png)
*`s`. One record per row — which this wave added; before it, a batch you sent vanished
without a line.*
![sent](screens/two-questions-in-one-step-are-one-panel-with-tabs-sent.png)
*`enter`. Both answers go through the one door in tab order, in one command, and each
leaves its own receipt.*

## Several approvals at once — one frame

![four reads in one batch](screens/four-reads-in-one-batch-are-one-permission-frame-raised.png)
*Four `read` calls the model asked for in one message are ONE frame: a row per thing
wanted, then `allow all 4 · one by one · deny all`. The pointer opens on `deny all` and
says `safe answer` — every permission opens on the answer that loses nothing until the
calls are graded (#953). There is no lifetime row: a consent's answer scope never reaches
the gate, so the frame offers only what it can honour.*

![one by one](screens/four-reads-in-one-batch-are-one-permission-frame-one-by-one.png)
*`2`. The same four open as tabs, each with its own keys, so a person who wants to read
the files before granting can.*

![the review, a mix held](screens/four-reads-in-one-batch-are-one-permission-frame-review.png)
*`1 1 1 3`. Three allowed and one denied, held rather than sent; the review offers
`send all 4`.*

![sent](screens/four-reads-in-one-batch-are-one-permission-frame-sent.png)
*`enter`. The deny goes through the same door as the allows, in one command.*

## Two windows

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
aforge fri 10:33pm
 Home  ? new conversation ×  + 
────────────────────────────────────────────────────────────────────────────────────────────────────


 · still yours to set · /budget sets what aforge may spend · /model and /crew pick the models
· esc interrupts · ctrl+c twice quits · ? for help

› Read ]8;;file:///tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-group/e.txt\/tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-]8;;\
group/e.txt, ]8;;file:///tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-group/f.txt\/tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-]8;;\
group/f.txt, ]8;;file:///tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-group/g.txt\/tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-]8;;\
group/g.txt, ]8;;file:///tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-group/h.txt\/tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-]8;;\
group/h.txt with the read tool — all four read calls in the same message, at once, not one ❮
 after another — and run nothing else. Then tell me each file's first line.

▾ reading 4 files in 2s · ↓ 99
 /tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-group
├─▶ read /tmp/TestQuestionsE2EFourReadsInOneBatchA…ermissionFrame272754868/002/q-group/f.txt ?
├─▶ read /tmp/TestQuestionsE2EFourReadsInOneBatchA…ermissionFrame272754868/002/q-group/g.txt ?
╰─▶ read /tmp/TestQuestionsE2EFourReadsInOneBatchA…ermissionFrame272754868/002/q-group/h.txt ?
▪  ↑ 14.2k ↓ 240

─ q-group · deepseek/deepseek-v4-flash · ⠿ auto · via wafer · main ───────────────── esc interrupt ─
╭─ ● e.txt ○ f.txt ○ g.txt ○ h.txt ○ review ────────────────────────────────────── 1 of 4 ─╮
│ │
│ ? needs your ok to run read │
│ /tmp/TestQuestionsE2EFourReadsInOneBatchAreOnePermissionFrame272754868/002/q-group/e.txt · tool… │
│ │
│ 1 allow once │
│ 2 always  │
│ ▸ 3 deny  safe answer │
│ │
╰─ ←→ question · ↑↓ choose · enter take it · esc later ────────────────────────────────────────────╯
 c change · ? ask back · 1–3 jump
› 
$0.0027 14.2k/1M · 1% YOLO running read · 2s · ? 4 questions · alt+a · waiting · your call
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading