Skip to content

feat(suggest): on-demand next-prompt suggestion deck (orb → fan → stack)#950

Open
edwin-zvs wants to merge 12 commits into
mainfrom
suggestion-deck
Open

feat(suggest): on-demand next-prompt suggestion deck (orb → fan → stack)#950
edwin-zvs wants to merge 12 commits into
mainfrom
suggestion-deck

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

What

An on-demand "suggestion deck" for any harness session (spec 0109): when a session is awaiting input, a corner orb appears on the session view. Click it (or C-x s) and the daemon generates a hand of suggested next prompts — one top pick plus 3–4 generated verb directions, each holding concrete prompt cards. The hand renders as a quarter-arc verb fan from the corner; picking a verb opens its card stack; accepting anything sends the text through the ordinary session.input path.

Design decisions (see specs/0109-suggestion-deck.md)

  • On-demand only — generation runs on orb activation, never automatically at turn end. Idle sessions cost nothing. [suggest] enabled = false disables it entirely.
  • Same-harness generation. Smith sessions use a new --suggest-mode process one-shot (mirrors --title-mode). Natively-forking harnesses (claude / codex / opencode / grok / pi) generate through a hidden fork probe of the target session: the harness resumes its own native conversation, so the provider's prompt cache already covers the full history — the request is one appended message, and the model predicts from complete context instead of a rendered tail. Other harnesses get a fresh hidden probe fed a rendered transcript tail. Probes are SessionKind::UsageProbe (hidden) and deleted after use.
  • Ephemeral event. SessionEvent::Suggestions is broadcast-only, never persisted (same treatment as AgentStatus). Any new turn / user message / terminal state invalidates the hand, and a monotonic per-session generation counter discards stale in-flight results.
  • Typing always wins. The overlay only consumes keys while open; any printable key closes it and takes its normal route to the PTY.
  • One shared contract. SuggestionHand::parse_loose (protocol crate) does the loose-in/strict-out parse + clamping for every generator.

Surface

  • protocol: SuggestionHand/Verb/Card, SessionEvent::Suggestions, session.suggest IPC, shared prompt + parser
  • daemon: request_suggestions + smith one-shot / fork-probe generation, [suggest] config
  • cli (TUI): orb badge (idle → spinner → count), fan, stack, keyboard (C-x s, arrows, ⏎, ⌫, esc) + full mouse support, ToggleSuggestions key action
  • smith: --suggest-mode

Demo (real end-to-end: smith session → orb → fan → stack → send → new turn)

▶ demo video

Verb fan (labels fully generated from the session):
fan

Card stack for the "recount" verb:
stack

(Media is pinned at commit 679012b, removed from the branch tip in the next commit.)

Testing

  • cargo test --workspace: green (1070 cli + daemon + e2e all pass)
  • Live smoke against an isolated daemon: session.suggest acks started:true, hidden probe never appears in construct list, and a real smith session produced a structurally-valid hand (top + 4 verbs × 2 cards) that broadcast to a subscribed client
  • vhs recording above exercises the full TUI loop including accept-and-send

Follow-ups (not in this PR)

  • Web UI rendering of the hand (it already receives the event via subscribe.events)
  • Fill-in-the-blank template cards; acceptance-feedback context
  • Anthropic cache_control breakpoints in the smith one-shot path

🤖 Generated with Claude Code

Turn-end sessions can now deal a hand of generated next prompts on
demand. A corner orb on the session view (C-x s or click) asks the
daemon via the new session.suggest IPC; generation runs on the
session's own harness — smith via a --suggest-mode one-shot, natively
forking harnesses (claude/codex/opencode/grok/pi) via a hidden fork
probe that reuses the provider prompt cache over the full history —
and the clamped hand broadcasts as an ephemeral, never-persisted
SessionEvent::Suggestions. The TUI renders it as a quarter-arc verb
fan and per-verb card stack; accepting sends through session.input.
Typing always wins: any printable key closes the overlay and takes
its normal route.
…cle fan

Clicks over the orb / open overlay were piped into mouse-grabbing
child PTYs (claude fullscreen) before construct's hit-testing ran, so
the orb never opened by mouse. The deck now gets a carve-out ahead of
forward_mouse_to_child, like the session-title menu and URL clicks.

The fan formula anchored chips on fixed rows and only bent a few
columns, rendering as a near-vertical column hugging the right edge.
Chips now sweep a real 15°–85° arc (aspect-corrected radii): the top
pick sits left of the orb, the last verb above it, one chip per row,
with a compact stair fallback for short panes.
@edwin-zvs

Copy link
Copy Markdown
Contributor Author

Fixed two issues found trying it live on the TUI:

  1. Mouse clicks on the orb now work over mouse-grabbing children. Clicks over the pane were piped into the child PTY (e.g. Claude Code fullscreen) before construct's hit-testing ran, so the orb never responded to the mouse. The deck now takes priority ahead of forward_mouse_to_child, same as the session-title menu and URL clicks. While the overlay is open, the whole pane acts as its backdrop (click-away closes instead of leaking into the child).
  2. The verb fan is now a true quarter-circle around the orb. The old formula pinned chips to fixed rows with only a few columns of bend — it rendered as a near-vertical column hugging the right edge. Chips now sweep a 15°–85° arc with aspect-corrected radii: top pick left of the orb, last verb above it, one chip per row, compact stair fallback for short panes.

Updated fan (v2 video):

fan v2

The fan grows upward from the orb, so Up now walks up the arc (toward
higher indices) and Down walks back toward the orb; the stack keeps
list semantics. The orb sheds the reversed-block look: while
generating it breathes through a diamond morph (◇◈◆✦) with creeping
dots after a plain-language 'thinking', a dealt hand shows ✦ with the
brief pulse, and the resting orb plays a short glint sweep every few
seconds so it feels alive without being busy.
Resting it watches the session — (◕‿◕) suggest — blinking every few
seconds and winking on every third cycle. While generating its
moon-phase eyes spin (◐‿◐)(◓‿◓)(◑‿◑)(◒‿◒) with creeping dots, a dealt
hand gets sparkle eyes (✦‿✦), and the open state smiles (^‿^) ✕.
Replaces the one-row glyph with a software-shaded sphere in the
ASCII-shader style (luminance → char ramp): an 11×5-cell ball lit by
an orbiting light with diffuse + specular shading on a phosphor
gradient of the theme accent, specular bleaching toward white. Cells
outside the silhouette stay untouched, so the sprite is transparent
against the pane. The face composites on top as dark glyphs over the
shaded cell color: blinks and winks at rest, moon-phase eyes spin
while thinking (the light also orbits faster), sparkle eyes when a
hand is dealt, happy eyes while open. Small panes keep the one-row
kaomoji fallback; the fan/stack now anchor to the orb's bottom row so
both orb forms share the same arc origin.
@edwin-zvs

Copy link
Copy Markdown
Contributor Author

The orb is now a shaded ASCII ball with a face — software-rendered in the ASCII-shader style: per-cell diffuse + specular lighting from an orbiting light, luminance mapped onto a ·:-=+*#@ ramp over a phosphor gradient of the theme accent, transparent outside its silhouette. It blinks/winks at rest, spins moon-phase eyes while thinking (the light orbits faster too), gets sparkle eyes when a hand is dealt. Small panes fall back to the one-row kaomoji. Also in this batch: fan Up/Down now match the visual direction.

resting / thinking / fan open (v3 video):

idle
thinking
fan

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