Skip to content

feat(tui): navigate the transcript by prompt, landmark and block - #53333

Closed
Nowaker wants to merge 1 commit into
anomalyco:devfrom
Nowaker:tui-block-nav
Closed

Nowaker wants to merge 1 commit into
anomalyco:devfrom
Nowaker:tui-block-nav

Conversation

@Nowaker

@Nowaker Nowaker commented Oct 5, 2026 •

Copy link
Copy Markdown

Note: I'm aware v1 is now the maintenance branch and new features go to v2 only. This PR is here for others who want to use it on v1. I will submit a v2 version once I've migrated my plugin setup to v2.

Issue for this PR

Closes #53331

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds three levels of transcript navigation, plus ctrl+home / ctrl+end for the existing first/last commands. Every binding is a default that tui.json can override.

keybind default stops on
messages_previous / messages_next ctrl+up / ctrl+down (was unbound) every user prompt
messages_landmark_previous / messages_landmark_next ctrl+shift+up / ctrl+shift+down user prompts, each turn's final response, question / todowrite / task calls
messages_block_previous / messages_block_next ctrl+alt+shift+up / ctrl+alt+shift+down every user prompt, assistant text part, reasoning part with content, tool call
messages_first / messages_last adds ctrl+home / ctrl+end unchanged behavior

Prompt navigation fix. messages_previous / messages_next used to skip any prompt within 10 rows of the viewport top, because they compared prompt positions against a fixed ±10 threshold rather than the row they land on. With no prompt left they scrolled a page. They now use the same picker as the other two levels, so every prompt is a stop, including short back-to-back turns. Past either end the view goes to the top or back to the bottom.

Landmark rule. The final response of a turn is the last non-empty text part before the next user prompt. While a turn is still streaming, its latest text part counts. The landmark tools are:

  • question: what you were asked and what you answered;
  • todowrite: the plan, as it changed;
  • task: where work was handed to a subagent.

Edits, patches, reads, greps and shell calls are not landmarks. A turn is mostly made of them, so stopping on each would make landmark navigation the same as block navigation.

Skipped reasoning. A reasoning part with no text after [REDACTED] is stripped is skipped. That covers encrypted blocks and GPT's empty "Thinking" placeholders, which render as a bare Thought · 1.0s line. It is the same test ReasoningPart uses to decide what it shows as content.

How it finds blocks. User messages were already rendered with id={message.id}. This PR gives text, reasoning, inline-tool and block-tool boxes id={part.id}, so the route can match a part to its scrollbox child. Selection lives in a small pure module, src/routes/session/navigation.ts:

  • navigationTargets(messages, parts, scope) returns the set of IDs to stop on for prompt, landmark or block.
  • pickNavigationTarget(blocks, anchor, direction) picks the nearest one above or below the anchor row. A part rendered as several boxes, such as apply_patch with one box per file, counts once, at its first box.

The anchor is one row below the viewport top, which is where messages_next already landed a prompt. A block that was just navigated to counts as current, so repeated presses walk one block at a time.

The keymap.test.tsx change only updates the count of default session.first bindings from 2 to 3.

How did you verify your code works?

  • test/routes/session/navigation.test.ts covers prompt, block and landmark selection:

    • synthetic and ignored prompts, empty and [REDACTED] reasoning, and whitespace-only text are excluded;
    • the final response of each turn, including the last turn while it streams;
    • nearest-above/below picking, multi-box parts, and both ends.

    bun test and bun typecheck pass in packages/tui.

  • I imported a 28-message session into isolated XDG_* dirs. It has 6 full turns, each with an empty GPT-style reasoning placeholder, reasoning with content, text, edit, bash, todowrite, alternating question/task, and a multi-paragraph final response, plus two one-line turns whose prompts are fewer than 10 rows apart. I drove it in a throwaway tmux socket with extended-keys on, sending the keys with tmux send-keys.

Screenshots / recordings

The first viewport row after each key, from tmux capture-pane, starting at the bottom of the session.

ctrl+up, before (messages_previous bound to ctrl+up in tui.json) and after:

before                               after
PROMPT 6: please do step 6           PROMPT 6: please do step 6
PROMPT 5: please do step 5           PROMPT 5: please do step 5
PROMPT 4: please do step 4           PROMPT 4: please do step 4
PROMPT SHORT A   <- skips SHORT B    PROMPT SHORT B
PROMPT 3: please do step 3           PROMPT SHORT A
                                     PROMPT 3: please do step 3

ctrl+shift+up and then ctrl+alt+shift+up:

C-S-Up   | PROMPT 6: please do step 6
C-S-Up   | FINAL 5: step 5 is done.
C-S-Up   | ✓ Explore Task — TASK 5
C-S-Up   | # Todos

C-M-S-Up | $ echo step 5
C-M-S-Up | ← Edit /src/step5.ts
C-M-S-Up | WORKING 5: starting on step 5.
C-M-S-Up | + Thought: 1.0s              <- reasoning with content; the empty placeholder before it is skipped
C-M-S-Up | PROMPT 5: please do step 5
C-M-S-Up | FINAL 4: step 4 is done.

On dev (907b3bc518), ctrl+up, ctrl+shift+up, ctrl+alt+shift+up, ctrl+home and ctrl+end don't change the viewport. home / end work as before. A draft in the prompt is left untouched; the prompt doesn't bind any of these keys.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

This PR is AI generated, but under direct supervision and on request of @Nowaker.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

A long session can only be moved through by line, page or the whole
transcript. messages_next/messages_previous exist but are unbound,
and they skip any prompt within 10 rows of the viewport top, because
they compare against a fixed threshold rather than the row they land
on. Assistant parts render without an id the scroll position could be
matched against.

Bind three levels of navigation by default, all overridable in
tui.json:

- messages_previous / messages_next (ctrl+up / ctrl+down) stop on
  every user prompt, now through the same picker as the two below.
- messages_landmark_previous / messages_landmark_next
  (ctrl+shift+up / ctrl+shift+down) stop on prompts, the last text
  part of each turn (its final response), and question, todowrite and
  task calls. Edits, reads and shell calls are left out because they
  are as dense as the blocks themselves.
- messages_block_previous / messages_block_next
  (ctrl+alt+shift+up / ctrl+alt+shift+down) stop on every prompt,
  text part, reasoning part with content, and tool call. Reasoning
  without content - an encrypted block or an empty "Thinking"
  placeholder, which GPT models emit on every step - renders as a
  bare header and is skipped.

Text, reasoning and tool boxes now carry their part id so the session
route can find them among the scrollbox children, as it already does
for user messages by message id. "Previous" and "next" are relative to
the row a navigated block lands on, one below the viewport top, so
repeated presses walk block by block; past either end the view goes
to the top or back to the bottom.

messages_first and messages_last also bind ctrl+home and ctrl+end.
@Nowaker Nowaker changed the title feat(tui): navigate the transcript by block and landmark feat(tui): navigate the transcript by prompt, landmark and block Oct 6, 2026
Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 6, 2026
…+shift

Prompt-to-prompt navigation is what ctrl+up/down are for:
messages_previous / messages_next take them by default, and block
navigation moves to ctrl+alt+shift+up/down. Landmarks keep
ctrl+shift+up/down.

messages_previous / messages_next now go through the same picker as
blocks and landmarks with a "prompt" scope. The vanilla commands
skipped any prompt within 10 rows of the viewport top, because they
compared against a fixed threshold rather than the row they land on,
and scrolled a page when nothing was left; now every prompt is a stop
and past either end the view goes to the top or back to the bottom.

Matches the amended upstream PR anomalyco#53333.

AI-Tool: opencode 1.18.34-vt-89-907b3bc518
AI-Model: anthropic/claude-opus-5-5
AI-Variant: max
AI-Platform: linux
AI-Harness: Vibeterm bd50370
AI-Session-ID: ses_ef46f1775ffe29MtElYF4DQySL
@thdxr

thdxr commented Oct 10, 2026

Copy link
Copy Markdown
Member

This branch has been upgraded to V2. We're no longer taking PRs for V1. If you think this is still relevant, please port it over and open it for V2.

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

@thdxr thdxr closed this Oct 10, 2026
Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 11, 2026
…+shift

Prompt-to-prompt navigation is what ctrl+up/down are for:
messages_previous / messages_next take them by default, and block
navigation moves to ctrl+alt+shift+up/down. Landmarks keep
ctrl+shift+up/down.

messages_previous / messages_next now go through the same picker as
blocks and landmarks with a "prompt" scope. The vanilla commands
skipped any prompt within 10 rows of the viewport top, because they
compared against a fixed threshold rather than the row they land on,
and scrolled a page when nothing was left; now every prompt is a stop
and past either end the view goes to the top or back to the bottom.

Matches the amended upstream PR anomalyco#53333.

AI-Tool: opencode 1.18.34-vt-89-907b3bc518
AI-Model: anthropic/claude-opus-5-5
AI-Variant: max
AI-Platform: linux
AI-Harness: Vibeterm bd50370
AI-Session-ID: ses_ef46f1775ffe29MtElYF4DQySL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: navigate the transcript by prompt, landmark and block (ctrl+up/down, ctrl+shift+up/down, ctrl+alt+shift+up/down, ctrl+home/end)

2 participants