mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-10 09:15:24 +00:00
* feat(web-shell): add split pane header action slot with overflow Let hosts render per-session actions in each split pane header, collapsing them into a … menu when the pane is too narrow. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(web-shell): add pane header actions PR screenshots Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): tighten pane header overflow measurement Drop the per-render children effect dependency that rebuilt ResizeObserver during streaming, and reserve workspace-tag width when computing available header space. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): address pane header overflow review blockers Mount host actions in only one tree, and wrap overflow entries as DropdownMenuItems so Radix selection and keyboard navigation work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): keep pane header actions alive across overflow Flatten Fragment host actions before building the overflow menu, and keep the same host instances mounted when collapsing so stateful actions are not reset. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): address pane header overflow review suggestions (#7808) * fix(web-shell): proxy overflow clicks via action slots Wrap host pane actions in stable slots so the overflow menu can activate interactive descendants without requiring opaque custom components to forward internal data attributes. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): address overflow menu review suggestions (#7808) * fix(web-shell): harden pane header overflow actions (#7808) Restore the 8px gap between the built-in maximize/close controls, ignore aria-hidden glyphs when labelling overflow items, omit non-interactive children from the overflow menu, and document the popover constraint on renderHeaderActions. Refreshes the design doc to match the mount-once implementation. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
192 lines
4.5 KiB
CSS
192 lines
4.5 KiB
CSS
.pane {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
height: 100%;
|
|
background: var(--background);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 10px;
|
|
/* On a multi-workspace pane, `--ws-accent` is set by a shared accent class
|
|
(workspaceAccent.module.css) and colorizes this divider into a per-workspace
|
|
strip; it falls back to the neutral border everywhere else, so single-
|
|
workspace panes are unchanged. */
|
|
border-bottom: 1px solid var(--ws-accent, var(--border));
|
|
background: color-mix(in srgb, var(--foreground) 3%, transparent);
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
/* The `--ws-accent` variable that colorizes the divider above and the tag below
|
|
is set by a shared accent class from workspaceAccent.module.css (also used by
|
|
the composer chip), applied to `.header` when the pane has a workspace. */
|
|
|
|
/* Which workspace this pane's session lives in — a compact colored chip at the
|
|
start of the header, shown only on a multi-workspace daemon. The dot never
|
|
shrinks, so panes stay distinguishable by color even when the name and the
|
|
session title both ellipsize on a narrow split; the full cwd is in the
|
|
native title tooltip. */
|
|
.workspaceTag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
flex: 0 1 auto;
|
|
min-width: 0;
|
|
max-width: 50%;
|
|
padding: 2px 8px 2px 6px;
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--ws-accent) 14%, transparent);
|
|
color: color-mix(in srgb, var(--ws-accent) 70%, var(--foreground));
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.workspaceTagDot {
|
|
width: 7px;
|
|
height: 7px;
|
|
flex: 0 0 7px;
|
|
border-radius: 999px;
|
|
background: var(--ws-accent);
|
|
}
|
|
|
|
.workspaceTagText {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Per-pane connection-loss indicator: each pane has its own daemon connection,
|
|
so a drop on one shouldn't silently leave stale messages with no signal. */
|
|
.connectionError {
|
|
flex: 0 0 auto;
|
|
padding: 6px 10px;
|
|
background: var(--warning-bg);
|
|
border-bottom: 1px solid var(--warning-border);
|
|
color: var(--warning-color);
|
|
font-size: 12px;
|
|
}
|
|
.connectionErrorText {
|
|
display: block;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.title {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--foreground);
|
|
}
|
|
|
|
.headerActions {
|
|
position: relative;
|
|
flex: 0 0 auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.headerActionsInline {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.headerActionSlot {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Keep host actions mounted while collapsed so stateful actions survive
|
|
resize. Absolutely positioned so they do not affect flex layout. */
|
|
.headerActionsHostHidden {
|
|
position: absolute;
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The built-in maximize/close controls used to be direct children of `.header`
|
|
(gap 8px); keep that spacing now that they are grouped here. */
|
|
.headerTrailing {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.headerOverflowPanel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 4px;
|
|
padding: 2px;
|
|
}
|
|
|
|
.headerActionButton,
|
|
.closeButton,
|
|
.maximizeButton {
|
|
flex: 0 0 auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 26px;
|
|
height: 26px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
color: var(--muted-foreground);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.headerActionButton:hover,
|
|
.closeButton:hover,
|
|
.maximizeButton:hover {
|
|
color: var(--foreground);
|
|
background: color-mix(in srgb, var(--foreground) 8%, transparent);
|
|
}
|
|
|
|
/* The maximized pane keeps its toggle visually "active" so it's clear which
|
|
control returns to the tiled layout. */
|
|
.maximizeButton[aria-pressed='true'] {
|
|
color: var(--foreground);
|
|
background: color-mix(in srgb, var(--foreground) 10%, transparent);
|
|
}
|
|
|
|
/* The transcript takes the remaining height and scrolls independently per pane. */
|
|
.body {
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.footer {
|
|
flex: 0 0 auto;
|
|
padding: 8px;
|
|
position: relative;
|
|
}
|
|
|
|
.approval {
|
|
margin-bottom: 8px;
|
|
}
|