Skip to content

Dock run state into the composer card and add prompt-card previews / 运行态内嵌输入卡与审批·提问卡预览行#6164

Merged
SivanCola merged 9 commits into
esengine:main-v2from
SivanCola:feature/composer-run-dock
Jul 8, 2026
Merged

Dock run state into the composer card and add prompt-card previews / 运行态内嵌输入卡与审批·提问卡预览行#6164
SivanCola merged 9 commits into
esengine:main-v2from
SivanCola:feature/composer-run-dock

Conversation

@SivanCola

Copy link
Copy Markdown
Collaborator

Summary

Redesign of the composer run state, plus interaction fixes and prompt-card polish that fell out of testing it. Frontend-only (desktop React UI, locales, CSS, frontend tests).

Run state docks into the composer card

  • The floating run-status pill above the composer is replaced by a run strip inside the card's top edge, so starting/finishing a turn no longer shifts the surrounding layout. User-resized cards grow by the strip's reserved height while it is visible, so the meta row is never clipped (fixed-height cards previously overflowed into the status bar).
  • The strip is a small state machine: retry > waiting-approval > waiting-ask > streaming. While a tool approval or ask question is blocked on the user, the strip says so (with the pending tool label) instead of ticking whimsical "working" seconds, and the composer hands its running accent off to the prompt card.
  • Stop gets a fixed home next to the send button while a turn runs; send switches to a queue-guidance icon. Enter still queues guidance.
  • Accessibility: the per-second ticker was inside an aria-live region and re-announced every second; it is now aria-hidden with a separate sr-only status region that announces only stable state transitions.

Interaction fixes found while testing

  • The approval bar (ask/auto/yolo) stays usable while its own prompt disables the composer, making the controller's designed mode-switch semantics reachable from the UI: relaxing the mode resolves drainable pending approvals in place (fresh-human tools are never drained), and surviving requests get an explanatory hint on the approval card.
  • Stopping a turn no longer silently discards queued guidance — unconsumed items fold back into the draft, matching the contract onCancel already honors for un-sent text.
  • The run ticker no longer counts approval/ask wait time as model time; waiting spans are subtracted (retries keep counting).

Prompt card polish

  • Ask card: options move from a full-width single column (~400px card) to a responsive multi-column grid with a right-aligned pill submit row (~230px for four options), plus a quick-pick-style detail row that previews the full description of the hovered/focused option (grid cells truncate to one line). Native title tooltips as fallback.
  • Tool approval card: the same pattern previews each action's consequence (allow-once vs session vs persistent vs deny — scope and persistence were previously unexplained). The subject block stays default-visible and untouched: what you approve must remain fully inspectable.
  • exit_plan_mode now maps to a friendly label — the run strip previously showed the raw tool name while a plan waited for confirmation.

PromptShelf gains an opt-in note slot and PromptAction gains title/onHoverChange; both are inert for existing callers.

Tests

  • New composer-run-strip.test.tsx (24 assertions): strip placement/a11y, stop button, waiting states, approval-bar scoping, guidance restore on cancel, and a real-time pause-accounting case.
  • approval-modal-file-reference.test.tsx +4: consequence row follows selection/arrows/hover, title fallback; the "full subject visible by default" contract is asserted unchanged.
  • ask-card-layout.test.ts +3: detail row default/hover behavior, title fallback.
  • composer-goal-toggle.test.tsx updated for the new stop button and placeholder.
  • Green: tsc --noEmit (src + test configs), check:css, and the full frontend test chain, re-run after rebasing onto latest main-v2.
  • Manually verified against a live dev instance: zero layout shift across idle/running/idle (pixel-identical card rects), fixed-height clipping regression, waiting-approval strip, mode-switch drain behavior, guidance restore, and both detail preview rows.

Cache-impact: none - desktop frontend only (React UI, locales, CSS, frontend tests); no prompts, tool schemas, or provider-visible bytes change.
Cache-guard: not applicable - no prompt or tool surface in the changed set.
System-prompt-review: no files under internal/ change in this PR.

@SivanCola SivanCola requested a review from esengine as a code owner July 7, 2026 19:01
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) labels Jul 7, 2026
SivanCola added 7 commits July 8, 2026 03:33
Replace the floating run-status pill with a run strip inside the
composer card, so starting or finishing a turn no longer shifts the
surrounding layout, and give stop a fixed home next to the send button
while a turn runs (send switches to a queue-guidance icon).

The strip is a small state machine: retry > waiting-approval >
waiting-ask > streaming. While a tool approval or ask question is
blocked on the user, the strip says so (with the pending tool label)
instead of ticking whimsical "working" seconds, and the card hands the
running accent off to the prompt card above it.

Accessibility: the per-second ticker was inside an aria-live region and
re-announced every second; it is now aria-hidden, with a separate
sr-only status region that announces only stable state transitions.

The approval card also gains a hint when the approval mode is relaxed
(ask -> auto/yolo) while a request is pending, since switching modes
does not auto-resolve the pending prompt.

Verified: tsc --noEmit, tsc -p tsconfig.test.json, check:css, new
composer-run-strip test (16 assertions), and the full frontend test
chain all pass.
The in-flow strip added ~30px to the card's content height, which a
user-resized card (fixed --composer-height, minimum 104px) could not
absorb: the meta row overflowed the card bottom and was clipped behind
the app status bar. Position the strip absolutely on the card's top
edge instead — it no longer participates in height math (resized cards
keep their exact height) and turn start/finish causes zero layout
shift. The strip is non-interactive (pointer-events: none) so the
resize handle underneath keeps working.
The composer is disabled while a tool approval is pending, which also
disabled the ask/auto/yolo bar — making the controller's designed
mode-switch semantics unreachable from the UI: SetToolApprovalMode
drains pending approvals the relaxed posture allows (fresh-human tools
are never drained, approval.go drainLocked). Scope the disable so the
approval bar stays active exactly when the pending approval itself is
the reason: relaxing the mode now resolves drainable requests in place,
and for surviving fresh-human requests the approval card's
mode-switch hint explains that an explicit decision is still needed.

Verified live against the dev instance: a pending bash approval
auto-resolves when switching ask -> auto, the run strip returns from
waiting to streaming, and the composer stays disabled for
non-approval reasons (covered by composer-run-strip assertions).
The absolute-positioned tab overlapped the guidance shelf, context
cards, and pasted blocks when those sat above the card. Switch to an
in-flow row: auto-height cards grow naturally, and user-resized cards
get COMPOSER_RUN_STRIP_RESERVED (30px) added to --composer-height via
inline style while the strip is visible — so the meta row stays fully
visible and reverts when the turn ends.
The ask card used full-width single-column option cards (42px each)
with full-width back/submit rows, consuming ~400px+ of viewport. This
was the compact shelf's grid being overridden by the ask-specific
1fr rule at higher specificity.

Switch to a responsive multi-column grid (auto-fill, minmax 200px)
with slightly shorter items (36px), raise the scroll cap to
min(220px, 30vh), and move back/submit into a right-aligned pill row
with a subtle border-top separator. Four options now render in two
rows at ~93px total; the overall card height drops from ~400px to
~230px.
The multi-column grid truncates option descriptions to one line, and
narrower columns made the loss worse — a cut-off description is the
one piece of information the user needs before answering. Add a
quick-pick-style detail row pinned under the options: it previews the
full description of the hovered/focused option, falling back to the
latest selected option and then the first described option so it
never blanks. Each option also carries a native title tooltip as a
redundant fallback.

PromptShelf gains a `note` slot (between actions and quick actions)
and PromptAction gains `title`/`onHoverChange`; both are opt-in and
inert for existing callers (approval card, clear-context card).
The allow-once / allow-session / always-allow / deny pills carry real
semantic differences (scope, persistence) that the labels alone do not
explain. Reuse the ask card's quick-pick pattern: a consequence row
under the pills previews what the hovered or arrow-selected action will
do, with a native title fallback on each pill. The subject block stays
default-visible and untouched — what you approve must remain fully
inspectable.

The tool actions are now a descriptor list driving both the buttons and
the preview, replacing the hand-rolled branch JSX. Also map
exit_plan_mode to a friendly label ("Plan approval") — the composer run
strip previously showed the raw tool name while a plan was waiting.
@SivanCola SivanCola force-pushed the feature/composer-run-dock branch from d052eff to fb1840f Compare July 7, 2026 19:34
Review found the strip compensation baked into the React render path
only: composerCardStyle wrote height+30px while the live drag updater
and drag-start measurement wrote raw logical heights to the same CSS
variable. Dragging a resized composer during a running turn briefly
shrank the card by the strip height (re-clipping the meta row) until
release re-added the compensation — a visible jump.

Make the variable single-meaning: --composer-height always carries the
user's logical height from every writer (render, live drag, keyboard),
and the strip reservation moves into a separate
--composer-run-strip-reserved variable consumed by a CSS calc on
.composer-card--resized. DOM-based fallback measurements subtract the
strip so auto-height cards convert to the same space.

Regression covered in composer-run-strip: drag start/release during a
running turn stays in logical-height space, the reservation survives
the drag, and the idle card releases it.
@SivanCola SivanCola merged commit f501f3d into esengine:main-v2 Jul 8, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant