qwen-code/packages/web-shell/client/components/StatusBar.module.css
carffuca fce79dd10b
feat(web-shell): friendlier Esc interruption + queued-prompt UX (#6025)
* fix(web-shell): prevent queued-prompt loss from drain race

The auto-drain effect popped a queued prompt, called setQueuedPrompts,
then submitted via setTimeout(0). Because the daemon flips streamingState
asynchronously, the setState re-render could re-run the effect and pop a
second prompt before the first registered as streaming — both submitted
back-to-back and the first was lost.

Arm an "awaiting turn start" gate synchronously at pop so the re-run is
blocked until streamingState goes non-idle, released by a dedicated
effect with a safety-net timer for a prompt that never streams (e.g. a
queued slash command). Cleanup no longer cancels/re-queues the pending
submit while the gate is armed.

* feat(web-shell): friendlier Esc interruption + queued-prompt UX

* refactor(web-shell): tidy Esc/queue code per review

Behavior-preserving cleanups addressing review feedback on the Esc-interruption
and queued-prompt changes:

- Remove the now-dead queue.footer i18n key (EN + ZH) and the unreferenced
  .queuedHint CSS, orphaned when the Esc-clears-queue behavior was dropped.
- Co-locate the queued-prompt styles in QueuedPromptDisplay.module.css instead
  of reaching into the parent App.module.css.
- Make the Esc confirm-window constants the single source of truth: export them
  from escapeIntent.ts and drive the countdown-ring duration from one of them
  via a CSS custom property.
- Nudge the queue-drain safety net with a dedicated tick counter instead of
  cloning queuedPrompts, so it no longer re-renders the composer for a no-op.
- Drop a redundant !compact guard in StatusBar left over from flattening a
  ternary.
- Document the pop/gate-arm ordering invariant in the drain effect.
2026-06-30 01:27:16 +00:00

199 lines
3.4 KiB
CSS

.bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 2px;
margin-bottom: 10px;
font-size: 13px;
color: var(--muted-foreground);
flex-shrink: 0;
}
.left,
.right {
display: flex;
align-items: center;
gap: 10px;
}
.left {
position: relative;
}
.settingsButton {
/* Sits in the bar's 2ch left-padding gutter (plus a few px of the footer
margin) so the mode label keeps its input-text alignment — the gear takes
no flex space. The gutter alone (~15.6px) is narrower than the button, so
the extra -6px buys a visible gap between the icon and the mode label
instead of the two touching. font: inherit so the -2ch resolves against
the same font as the bar's 2ch padding; the 2px padding is hit area, free
since we're out of flow. */
left: calc(-2ch - 6px);
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px;
margin: 0;
border: none;
background: none;
font: inherit;
color: var(--muted-foreground);
cursor: pointer;
}
.settingsButton:hover,
.settingsButton:focus-visible {
color: var(--foreground);
}
.modeButton {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 0;
margin: 0;
border: none;
background: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.modeButton:hover .modeHint,
.modeButton:focus-visible .modeHint {
color: var(--muted-foreground);
}
.modeLabel {
font-size: 13px;
}
.modeDefault {
color: var(--muted-foreground);
}
.modePlan {
color: var(--success-color);
}
.modeAutoEdit {
color: var(--warning-color);
}
.modeAuto {
color: var(--warning-color);
}
.modeYolo {
color: var(--error-color);
}
.modeHint {
color: var(--muted-foreground);
font-size: 12px;
}
.shortcutsButton {
display: inline-flex;
align-items: center;
padding: 0;
margin: 0;
border: none;
background: none;
font: inherit;
font-size: 12px;
color: var(--muted-foreground);
cursor: pointer;
}
.shortcutsButton:hover,
.shortcutsButton:focus-visible {
color: var(--foreground);
}
.modelButton {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0;
margin: 0;
border: none;
background: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.modelButton:hover .model,
.modelButton:focus-visible .model {
color: var(--foreground);
text-decoration: underline;
}
.model {
color: var(--muted-foreground);
font-size: 12px;
}
.contextButton {
display: inline-flex;
align-items: center;
padding: 0;
margin: 0;
border: none;
background: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.contextButton:hover .context,
.contextButton:focus-visible .context {
color: var(--foreground);
text-decoration: underline;
}
.context {
font-size: 11px;
color: var(--muted-foreground);
}
.goal {
font-size: 12px;
color: var(--agent-blue-500);
white-space: nowrap;
}
.separator {
color: var(--muted-foreground);
}
.taskPill {
border: 0;
border-radius: 0;
padding: 0 4px;
background: transparent;
color: var(--muted-foreground);
font: inherit;
font-size: 12px;
cursor: pointer;
}
.taskPill:hover,
.taskPill:focus-visible {
background: var(--muted-foreground);
color: var(--background);
outline: none;
}
.taskPill:disabled {
cursor: default;
}
@media (max-width: 700px) {
.settingsButton,
.modelButton,
.modeHint {
display: none;
}
}