mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-19 14:04:06 +00:00
* 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.
199 lines
3.4 KiB
CSS
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;
|
|
}
|
|
}
|