Skip to content

feat(tui): show the session goal under the prompt - #59275

Closed
neriousy wants to merge 1 commit into
session-goalfrom
goal-tui
Closed

neriousy wants to merge 1 commit into
session-goalfrom
goal-tui

Conversation

@neriousy

@neriousy neriousy commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Issue for this PR

Stacked on #59262 (/goal). Merge that first; this PR then retargets to dev.

Type of change

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

What does this PR do?

Shows the session's goal under the TUI prompt, next to the other session status, and keeps goal changes live in the client store.

flowchart LR
    Plugin["goal plugin<br/>writes metadata"] -->|session.metadata.updated| Store["client store<br/>(now handled)"]
    Store --> Status["prompt footer<br/>◎ goal · ◎ goal paused · ◎ goal met"]
    Status -->|click| Dialog["goal dialog<br/>objective · pause · resume · clear"]
    Dialog -->|/goal pause| Plugin
Loading
  • Client store: packages/client/src/solid/data.ts ignored session.metadata.updated, so metadata changed only on a full re-read. It now replaces the Session's metadata from the event. It uses reconcile, because the event carries the whole record and dropped keys must go.
  • Goal status (GoalStatus in feature-plugins/prompt/goal.tsx, rendered by prompt/footer.tsx): a short item in the footer under the prompt, beside the subagent/shell counts and usage. It reads ◎ goal, ◎ goal paused or ◎ goal met.
    • It shows only the state. The objective lives in the dialog, so a long objective never crowds the footer.
    • Active is muted, so a running goal stays quiet. Paused uses the warning role and met the success role.
    • Goal continuations are hidden from the timeline (feat(core): add /goal to keep a session working until an objective is met #59262), so this is where a running goal shows.
    • There's no round counter.
    • It decodes the goal with SessionGoal.fromMetadata, so a goal a child Session copied from its parent isn't shown.
  • Goal dialog: clicking the status opens a dialog with the objective, the status and the last reason. It offers p pause, r resume and x clear, depending on the status, runs the server's /goal subcommands, and follows the live state.
  • Timeline: the goal's kickoff notice ("Goal: …") uses ◎ instead of the generic ◈.
  • Story: OPENCODE_STORY=goal bun run dev:live renders the real prompt footer with a fixture goal. s cycles the status, l toggles a long objective, and x/r clear and reset. Actions update the fixture only.

How did you verify your code works?

  • packages/client/test/solid-data.test.ts: a metadata update adds a key, and a later update without it removes it. This fails with a plain setStore, which merges.
  • packages/tui/test/feature-plugins/prompt-footer.test.tsx:
    • the footer shows ◎ goal in the muted colour, without the objective or a round counter, and opens the dialog on click;
    • it switches to ◎ goal paused in the warning colour;
    • it hides a goal that names another Session.
  • Drove the real TUI against this branch with opencode-drive:
    • ◎ goal appeared under the prompt on /goal and stayed while two long reports scrolled the kickoff row away;
    • the continuation added no timeline row;
    • the status turned to ◎ goal met, and clicking it opened the dialog with the objective and the reviewer's reason;
    • a second goal stopped with Esc showed ◎ goal paused.
  • bun run check passes.

Visual

A looping animation for the desktop app's "What's new" dialog. GitHub only shows its source.

SVG source
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect x="12" y="20" width="216" height="32" rx="6" opacity="0.25"/>
  <line x1="26" y1="36" x2="90" y2="36" opacity="0.3"/>
  <circle cx="128" cy="36" r="6"/>
  <circle cx="128" cy="36" r="2" fill="currentColor"/>
  <g opacity="0"><animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.3;0.32;1" dur="4s" repeatCount="indefinite"/>
    <line x1="144" y1="36" x2="164" y2="36" opacity="0.7"/><line x1="172" y1="36" x2="180" y2="36" opacity="0.7"/>
  </g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.3;0.32;0.62;0.64;1" dur="4s" repeatCount="indefinite"/>
    <line x1="144" y1="36" x2="164" y2="36" opacity="0.7"/><line x1="172" y1="36" x2="196" y2="36" opacity="0.7"/>
  </g>
  <g><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.62;0.64;1" dur="4s" repeatCount="indefinite"/>
    <path d="M146 36l6 6 12-12" stroke-width="2.5"/><line x1="174" y1="36" x2="206" y2="36" opacity="0.7"/>
  </g>
</svg>

Screenshots / recordings

Real TUI and server from this stack, driven with opencode-drive. Only the model and reviewer replies are scripted.

The whole loop: /goal → a continuation → met → the dialog → a second goal stopped with Esc.

Recording of the goal loop in the TUI

Start: ◎ goal under the prompt, at the right.
Goal status after /goal
Long output: the start row scrolled away and the status stays. The continuation adds no row.
Goal status while output scrolls
Met.
Goal met
Click the status: objective, status, reason and actions.
Goal dialog
Stopped with Esc: the goal pauses.
Goal paused

Checklist

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

@neriousy
neriousy added this pull request to stack #59279 October 26, 2026 13:04
@neriousy neriousy changed the title feat(tui): show the session goal in the prompt footer feat(tui): pin the session goal above the prompt Oct 26, 2026
@neriousy
neriousy force-pushed the goal-tui branch 3 times, most recently from 8fa9596 to cc379a3 Compare October 26, 2026 14:18
@neriousy neriousy changed the title feat(tui): pin the session goal above the prompt feat(tui): show the session goal under the prompt Oct 26, 2026
@neriousy

Copy link
Copy Markdown
Member Author

Folded into #58508, which now holds the whole /goal stack (idle hook, settled notifications, /goal, TUI and desktop) in one PR.

@neriousy neriousy closed this Oct 26, 2026
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.

1 participant