Skip to content

A question whose answers do not fit the row is drawn as a row anyway, with an answer cut off: questionRowFits has no callers #1005

Description

@santoshkumarradha

What happened

On 2026-09-12, against fix/e2e-question-needles (e05b0ffa5, a branch off dev@0ce30c776 touching only internal/e2e and one manual line), TestQuestionsE2E/AssumptionsStandUntilOneIsStruck on deepseek/deepseek-v4-flash, at 100 columns, drew a three-answer question as one row with the third answer gone and the second cut mid-word:

─ q-assumption · deepseek/deepseek-v4-flash · ⠿ auto · via wafer · main ──────────── esc interrupt ─
≈ I am assuming these before I write the migration  ▸1 the ledger is append only   2 nobody reads i…
  none of them is written down anywhere I can read
  ←→ choose · enter take it · esc later · c change · ? ask back · goes on in 10m 0s

The asker sent three options — the ledger is append only, nobody reads it from a second process, the timestamps are UTC — and asked for form card. A person reading that screen cannot see, and cannot discover, that a third assumption is standing.

This is the one-row view's own stated law, broken:

// questionRowOffer is that row: the mark, the head, and every answer beside it,
// the pointed one on the selected ground.
//
// NO ANSWER IS EVER DROPPED OR CUT. A row too narrow for every answer is a row
// the chooser should not have chosen, so it says so by giving the question the
// panel instead ([app.questionRowFits] is asked before this is drawn).

questionRowFits is asked by nobody. On dev:

$ grep -rn 'questionRowFits' internal/tui3/
internal/tui3/question.go:1338:// panel instead ([app.questionRowFits] is asked before this is drawn).
internal/tui3/question.go:1362:// questionRowFits reports whether the one-row view can hold this question's head
internal/tui3/question.go:1366:func questionRowFits(q session.Question, width int) bool {

Two mentions in a comment and the definition. The chooser's last rung is unconditional:

{
    view: viewRow,
    why:  "everything left is a question whose whole decision is its answers' own words",
    when: func(a *app, q questionShown, width int) bool { return true },
},

Arithmetic for the screen above, from questionRowFits's own formula: 2 + 47 for the head, plus 3 + 1 + 1 + len(word) per answer — 49 + 30 + 41 + 27 = 147 cells wanted on a 100-cell row. The function would have said no. Nothing asked it.

It is not about the assumption kind. Any question with no consequence, no body, no evidence, no pick-with-a-reason and no call — the properties the ladder's earlier rungs read — falls to the last rung whatever its answers are worth in cells.

Replication

Deterministic (no model). A internal/tui3 unit test is enough, because the ladder is a pure function of the question and the width: build a session.Question with three long labels and no consequence/body/pick-reason/subject-call, and ask questionViewOf(q, 100). It answers viewRow, and plain(strings.Join(a.questionRows(100), "\n")) has the third label missing and the second ending in . grep -rn 'questionRowFits' internal/tui3/ is the one-line version of the same fact: the law has no caller.

Field (real models). Fires on every run, deterministically, in the suite that found it:

make build
go test -tags e2e -run 'TestQuestionsE2E/AssumptionsStandUntilOneIsStruck' -count=1 -timeout 15m -v ./internal/e2e/

Needs OPENROUTER_API_KEY (resolved by liveKey) and tmux; about nine seconds and a fraction of a cent. It fired identically on two consecutive runs (10:32 and 10:38 UTC, 2026-09-12), with the same steer and the same cut. The assertion it fails is the third assumption: the screen never said "the timestamps are UTC".

Where

  • questionLadder's last rung and questionViewOf in internal/tui3/questionchooser.go.
  • questionRowFits and questionRowOffer in internal/tui3/question.go (search NO ANSWER IS EVER DROPPED OR CUT).
  • The scenario that catches it: questionsAssumption in internal/e2e/questions_e2e_test.go.

The fix

The ladder is data and each rung is a property, so this is a property it is missing: a row that will not hold every answer is not a row. A rung above the final one, asking questionRowFits, giving viewPanel — which is what the comment on questionRowOffer has claimed all along.

It is a product change and not only a repair: questions that are drawn as one row today become panels at narrow widths, so it wants the owner's eye and a full internal/tui3 run. TestTheChooserIsOneLadderOfProperties reads the rungs' own prose and will want the new one's reason.

Acceptance

  • e2e: TestQuestionsE2E/AssumptionsStandUntilOneIsStruck passes on deepseek/deepseek-v4-flash at questionCols (100) — the screen carries the timestamps are UTC, spelled in full.
  • Unit: a internal/tui3 law that no width ever draws a question through questionRowOffer whose painted row is wider than the frame — i.e. questionRowFits is true wherever viewRow is chosen. A test that pins one width is a test the next long label walks past.
  • grep -rn 'questionRowFits' internal/tui3/ names a caller.

Found on 2026-09-12 while un-rotting the questions e2e needles (#998). Not caused by that work — the needle it fails is the timestamps are UTC, which is the asker's own word and has never gone through the needle table.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:chatThe v3 surface a person sits in front of (internal/tui3)bugSomething the code does that it should not

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions