Files
Meldwork/frontend/src/styles/base-sidebar.css
T
Ryder Sun ce995cb93d feat: refresh the app UI and surface what needs the user
The chat page (header, messages, composer, run status and the animated empty
state) and the modal dialogs are deliberately left as they were before this
refresh: those files are byte-identical to 17c57f5.

What changed
- Design tokens: an 8-step type scale (readable text no smaller than 12px
  outside badges), semantic success/warning/danger/info colors for both themes,
  AA-contrast muted and accent text, a focus-ring token, elevation, layer and
  motion tokens, and a dark backdrop that is darker than the workspace card.
- Startup is no longer blocked by Agent detection. The full-screen WebGL overlay
  and the physics loader are removed (three is dropped from the dependencies);
  the sidebar, home page and composer show "Detecting local Agents" instead, and
  an empty Agent list is not reported as a setup problem while detection runs.
- Needs your decision: conversations waiting on a permission or decision are
  marked in the sidebar (rows, Agent icons, collapsed switcher) and listed on the
  home page. Group runs that finish while you are elsewhere get the same finished
  mark direct chats already had. Collapsed sidebar sections show a count.
- Settings: a new General tab (language, appearance: System/Light/Dark). The
  theme now follows the OS until one is chosen; it used to freeze on first
  launch. Agent cards explain why an installed Agent is unavailable (installed
  version, verified range, failed check) with a Re-detect action. Cloud
  connections ask before deleting.
- Gemini: a detected Gemini CLI gets its own logo. It stays out of the built-in
  list so nobody sees a "Not installed" card for an Agent Meldwork cannot install.
- Toasts: the duration grows with the message length and pauses on hover; errors
  use the alert role.
- Onboarding: a Skip button and arrow-key navigation.
- Unsent text is kept per conversation while the app is open.
- Copy: engineering terms (circuit breaker, hard budget, delivery owner) are
  gone from user-facing status text.
- Status colors, focus ring and the run-details waiting state use the new tokens.

Why
Small text and low-contrast grey carried the most important state (Agent
availability, time, hints); the brand color meant ready, done, running and
warning at once; and the app could not be used until local Agent detection
finished although history is local. This makes states legible and separable and
shows the ones that need the user.

Checks
- npm --prefix frontend test: 43 files, 357 tests passed
- npm --prefix frontend run build and build:desktop: passed
- Not verified in a running Electron window; the changed screens need a visual
  check in light and dark, Chinese and English.
2026-10-16 23:25:46 +08:00

832 lines
17 KiB
CSS

.sidebar {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
background: transparent;
}
.brand-row {
height: 70px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 12px 8px 14px;
-webkit-app-region: drag;
}
.brand-button,
.sidebar-toggle,
.titlebar-actions {
-webkit-app-region: no-drag;
}
.titlebar-actions {
display: inline-flex;
position: fixed;
top: 6px;
right: 12px;
z-index: 20;
align-items: center;
gap: 2px;
flex: 0 0 auto;
padding: 2px 5px;
border-radius: 9px;
-webkit-app-region: no-drag;
}
.titlebar-actions .icon-button {
width: 28px;
height: 28px;
padding: 0;
border-color: transparent;
background: transparent;
color: var(--muted);
}
.titlebar-actions .icon-button:hover {
border-color: var(--border);
background: var(--surface-hover);
color: var(--text);
}
.brand-button {
min-width: 0;
display: flex;
align-items: center;
gap: 10px;
padding: 4px;
border: 0;
background: transparent;
text-align: left;
cursor: pointer;
}
.brand-button img {
width: 36px;
height: 36px;
flex: 0 0 auto;
display: block;
object-fit: contain;
}
.brand-button span {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.brand-button strong {
color: var(--text-soft);
font-size: var(--text-md);
line-height: 1.2;
}
.brand-button.active strong {
color: var(--accent-text);
}
.brand-button small {
overflow: hidden;
color: var(--muted);
font-size: var(--text-xs);
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon-button {
width: 34px;
height: 34px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: transparent;
color: var(--muted);
cursor: pointer;
}
.sidebar.collapsed .brand-row {
width: var(--sidebar-collapsed-width);
height: 96px;
align-self: flex-start;
flex-direction: column;
justify-content: center;
gap: 6px;
padding: 8px 0;
}
.sidebar.collapsed .brand-button {
width: 100%;
flex: 0 0 34px;
justify-content: center;
padding: 0;
}
.sidebar.collapsed .brand-button img {
width: 28px;
height: 28px;
}
.sidebar.collapsed .sidebar-toggle {
width: 34px;
height: 34px;
flex: 0 0 34px;
}
.sidebar.collapsed .brand-button span,
.sidebar.collapsed .new-group-button span,
.sidebar.collapsed .nav-heading,
.sidebar.collapsed .sidebar-agent-location-heading,
.sidebar.collapsed .sidebar-agent-main > span,
.sidebar.collapsed .sidebar-agent-new,
.sidebar.collapsed .direct-session-list,
.sidebar.collapsed .group-nav-section,
.sidebar.collapsed .nav-empty,
.sidebar.collapsed .sidebar-footer button > span {
display: none;
}
.sidebar.collapsed .new-group-button {
width: 38px;
margin: 2px 15px 12px;
padding: 0;
}
.sidebar.collapsed .conversation-nav {
width: var(--sidebar-collapsed-width);
align-self: flex-start;
padding-inline: 6px;
}
.sidebar.collapsed .sidebar-agent {
margin-block: 4px;
}
.sidebar.collapsed .sidebar-agent-header {
grid-template-columns: 1fr;
padding: 0;
}
.sidebar.collapsed .sidebar-agent-main {
grid-template-columns: 30px;
justify-content: center;
padding: 0;
}
.sidebar.collapsed .sidebar-footer button {
width: 34px;
grid-template-columns: 1fr;
justify-items: center;
padding: 0;
}
.sidebar.collapsed .sidebar-footer {
width: var(--sidebar-collapsed-width);
display: flex;
align-items: center;
align-self: flex-start;
flex-direction: column;
}
.sidebar.collapsed .sidebar-footer-actions {
flex-direction: column;
}
.collapsed-group-switcher {
width: var(--sidebar-collapsed-width);
flex: 0 0 auto;
align-self: flex-start;
display: flex;
justify-content: center;
padding: 4px 0 8px;
}
.collapsed-group-switcher-button {
position: relative;
width: 38px;
height: 38px;
color: var(--muted);
}
.collapsed-group-switcher-button:hover,
.collapsed-group-switcher-button:focus-visible,
.collapsed-group-switcher-button.active {
background: var(--surface-hover);
color: var(--text);
}
.collapsed-group-attention {
position: absolute;
top: 5px;
right: 5px;
}
.icon-button:hover {
border-color: var(--border);
background: var(--surface-hover);
color: var(--text);
}
.sidebar-toggle:hover,
.sidebar-toggle:focus-visible {
border-color: transparent;
background: transparent;
color: var(--text);
}
.new-group-button {
height: 38px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin: 2px 12px 12px;
border: 1px solid var(--accent-strong);
border-radius: var(--radius);
background: var(--accent-strong);
color: var(--accent-contrast);
font-weight: 600;
cursor: pointer;
transition:
background-color var(--dur-fast) var(--ease-standard),
border-color var(--dur-fast) var(--ease-standard),
transform var(--dur-press) var(--ease-standard);
}
.new-group-button:hover,
.primary-button:hover:not(:disabled),
.send-button:hover:not(:disabled) {
border-color: var(--accent-strong-hover);
background: var(--accent-strong-hover);
}
/* Pressing scales slightly instead of shifting, so nothing next to the
button moves with it. The composer's send and stop buttons keep their
original one-pixel press. */
.new-group-button:active,
.primary-button:active:not(:disabled),
.secondary-button:active:not(:disabled),
.danger-button:active:not(:disabled),
.sidebar-more-button:active {
transform: scale(0.97);
}
.send-button:active,
.stop-button:active {
transform: translateY(1px);
}
.conversation-nav {
min-height: 0;
flex: 1;
overflow: auto;
padding: 0 8px 14px;
}
.nav-section + .nav-section {
margin-top: 18px;
}
.nav-heading {
width: 100%;
height: 28px;
display: flex;
align-items: center;
gap: 6px;
border: 0;
border-radius: var(--radius-sm);
padding: 0 8px;
background: transparent;
color: var(--muted);
font-size: var(--text-xs);
font-weight: 600;
text-align: left;
cursor: pointer;
transition: color var(--dur-fast) var(--ease-standard);
}
/* A collapsed section shows how much it hides. */
.nav-heading-count {
min-width: 18px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 5px;
border-radius: 999px;
background: var(--surface-active);
color: var(--muted);
font-size: var(--text-2xs);
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.nav-heading:hover,
.nav-heading:focus-visible {
color: var(--text);
}
.sidebar-agent {
margin: 2px 0 8px;
}
.sidebar-agent-location-group + .sidebar-agent-location-group {
margin-top: 10px;
}
.sidebar-agent-location-heading {
margin: 6px 8px 3px;
color: var(--muted);
font-size: var(--text-xs);
font-weight: 600;
line-height: 1.4;
}
.sidebar-agent-header {
height: 42px;
display: grid;
grid-template-columns: minmax(0, 1fr) 30px;
align-items: center;
gap: 4px;
padding: 0 3px;
}
.sidebar-agent-main {
min-width: 0;
height: 38px;
display: grid;
grid-template-columns: 28px minmax(0, 1fr);
align-items: center;
gap: 8px;
padding: 0 6px;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: transparent;
text-align: left;
cursor: pointer;
transition: color 0.14s ease, opacity 0.14s ease;
}
.sidebar-agent-new:hover {
border-color: var(--border);
background: var(--surface-hover);
}
.direct-session-action:hover {
background: var(--surface-hover);
}
.sidebar-agent-main:hover,
.sidebar-agent-main:focus-visible {
color: var(--text);
}
.sidebar-agent-main:hover strong,
.sidebar-agent-main:focus-visible strong {
color: var(--text);
}
.sidebar-agent-main img {
width: 28px;
height: 28px;
background: transparent;
object-fit: contain;
}
.agent-logo-cloud {
position: relative;
width: 28px;
height: 28px;
display: block;
}
.agent-attention-dot {
position: absolute;
top: -2px;
right: -3px;
z-index: 2;
}
.sidebar-agent.unavailable .sidebar-agent-main img {
filter: grayscale(1);
opacity: 0.55;
}
.sidebar-agent-main small[data-tone="warning"] {
color: var(--warning);
}
.agent-logo-cloud > svg {
position: absolute;
right: -4px;
bottom: -3px;
width: 13px;
height: 13px;
padding: 2px;
border-radius: 50%;
background: var(--sidebar);
color: var(--accent);
}
.sidebar-agent-main > span {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.sidebar-agent-main strong,
.sidebar-agent-main small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar-agent-main strong {
color: var(--text-soft);
font-size: var(--text-sm);
}
.sidebar-agent-main small {
color: var(--muted);
font-size: var(--text-xs);
}
.sidebar-agent-new,
.direct-session-action {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: transparent;
color: var(--muted);
cursor: pointer;
}
.sidebar-agent-new {
width: 28px;
height: 28px;
}
.sidebar-agent-new svg,
.direct-session-action svg {
width: 14px;
height: 14px;
}
.direct-session-list {
--session-tree-line: color-mix(in srgb, var(--border) 78%, transparent);
position: relative;
margin: 0 3px 0 19px;
padding: 2px 0 2px 12px;
}
.group-conversation-list {
margin: 0;
padding: 0;
}
.direct-session-list::before {
content: "";
position: absolute;
top: 0;
bottom: 18px;
left: 0;
width: 9px;
border-bottom: 1px solid var(--session-tree-line);
border-left: 1px solid var(--session-tree-line);
border-bottom-left-radius: 7px;
background: transparent;
pointer-events: none;
}
.direct-session-list:has(> .sidebar-more-button)::before {
bottom: 18px;
}
.direct-session-row {
position: relative;
min-width: 0;
height: 34px;
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto auto;
align-items: center;
gap: 2px;
padding: 0 3px 0 6px;
border: 1px solid transparent;
border-radius: var(--radius-sm);
}
.direct-session-row::before,
.direct-session-list > .sidebar-more-button::before {
content: "";
position: absolute;
top: 50%;
left: -12px;
width: 8px;
height: 1px;
border-radius: 1px;
background: var(--session-tree-line);
pointer-events: none;
}
.direct-session-list > :last-child::before {
content: none;
}
.direct-session-row:hover {
background: var(--surface-hover);
}
.direct-session-row.active {
border-color: transparent;
background: var(--surface-active);
}
.direct-session-row.active .direct-session-open {
color: var(--text);
font-weight: 600;
}
.direct-session-row.active::after {
content: "";
position: absolute;
top: 7px;
bottom: 7px;
left: 0;
width: 3px;
border-radius: 999px;
background: var(--accent);
}
.direct-session-open {
grid-column: 1;
min-width: 0;
height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
padding: 0;
border: 0;
background: transparent;
color: var(--text-soft);
font-size: var(--text-sm);
text-align: left;
cursor: pointer;
}
.direct-session-open span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.direct-session-action {
grid-column: 2;
grid-row: 1;
width: 24px;
height: 24px;
opacity: 0;
pointer-events: none;
transform: translateX(3px);
transition: opacity 0.14s ease, transform 0.16s ease, background 0.14s ease, color 0.14s ease;
}
.direct-session-row.active .direct-session-action,
.direct-session-row:hover .direct-session-action,
.direct-session-row:focus-within .direct-session-action {
opacity: 0.62;
pointer-events: auto;
transform: translateX(0);
}
.group-conversation-row:hover .direct-session-action,
.group-conversation-row:focus-within .direct-session-action {
opacity: 0.62;
pointer-events: auto;
transform: translateX(0);
}
.direct-session-action:hover,
.direct-session-action:focus-visible {
opacity: 1;
}
.direct-session-action.danger:hover {
background: var(--danger-soft);
color: var(--danger);
}
.sidebar-delete-control {
grid-column: 3;
grid-row: 1;
position: relative;
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.direct-session-spacer {
grid-column: 4;
grid-row: 1;
width: 0;
}
.direct-session-row > .run-mark,
.direct-session-row > .run-finished-mark,
.direct-session-row > .attention-mark {
grid-column: 4;
grid-row: 1;
width: 18px;
justify-self: end;
}
.direct-session-row.active > .run-mark {
display: none;
}
.conversation-link {
width: 100%;
min-width: 0;
height: 40px;
display: grid;
grid-template-columns: 26px minmax(0, 1fr) auto;
align-items: center;
gap: 9px;
padding: 0 9px;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-soft);
font-size: var(--text-sm);
cursor: pointer;
text-align: left;
}
.group-conversation-row {
--row-background: var(--bg);
position: relative;
border-radius: var(--radius-sm);
}
.group-conversation-row:hover,
.group-conversation-row:focus-within {
--row-background: var(--surface-hover);
}
.group-conversation-row.active {
--row-background: var(--surface-active);
background: var(--surface-active);
}
.group-conversation-row.active .conversation-link {
color: var(--text);
font-weight: 600;
}
/* Row actions float over the end of the name with a short fade, so the name
can use the full row width when they are hidden. */
.group-conversation-actions {
position: absolute;
top: 50%;
right: 4px;
display: inline-flex;
align-items: center;
gap: 2px;
padding-left: 18px;
border-radius: var(--radius-sm);
background: linear-gradient(90deg, transparent, var(--row-background) 16px);
opacity: 0;
pointer-events: none;
transform: translateY(-50%) translateX(4px);
transition:
opacity var(--dur-fast) var(--ease-standard),
transform var(--dur-base) var(--ease-out);
}
.group-conversation-row.active .group-conversation-actions,
.group-conversation-row:hover .group-conversation-actions,
.group-conversation-row:focus-within .group-conversation-actions {
opacity: 1;
pointer-events: auto;
transform: translateY(-50%) translateX(0);
}
.conversation-link:hover {
color: var(--text);
}
.group-conversation-row:hover:not(.active),
.group-conversation-row:focus-within:not(.active) {
background: var(--surface-hover);
}
.sidebar-more-button {
position: relative;
width: 100%;
height: 30px;
display: flex;
align-items: center;
justify-content: flex-start;
margin: 1px 0 0;
padding: 0 8px;
border: 0;
border-radius: var(--radius-sm);
background: transparent;
color: var(--muted);
font-size: var(--text-xs);
font-weight: 600;
cursor: pointer;
text-align: left;
transition: background 0.14s ease, color 0.14s ease, transform 0.14s ease;
}
.sidebar-more-button:hover,
.sidebar-more-button:focus-visible {
background: var(--surface-hover);
color: var(--text);
}
.sidebar-more-button span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.conversation-link > img,
.group-avatar {
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
object-fit: contain;
background: transparent;
}
.group-avatar svg {
width: 15px;
height: 15px;
color: var(--accent);
}
.group-avatar.stack {
position: relative;
}
.group-avatar.stack img {
position: absolute;
width: 17px;
height: 17px;
object-fit: contain;
}
.group-avatar.stack img:nth-child(1) { top: 0; left: 0; }
.group-avatar.stack img:nth-child(2) { right: 0; bottom: 0; }
.group-avatar.stack img:nth-child(3) { left: 0; bottom: 0; }
.titlebar-preferences { display: flex; gap: 2px; }
.app-shell:not([data-platform="darwin"]) { --desktop-titlebar-height: 32px; }
.titlebar-actions .shortcut-menu { top: calc(100% + 8px); right: 0; }
@media (prefers-reduced-motion: no-preference) {
.sidebar:not(.collapsed) .brand-button span,
.sidebar:not(.collapsed) .new-group-button span,
.sidebar:not(.collapsed) .nav-heading,
.sidebar:not(.collapsed) .sidebar-agent-location-heading,
.sidebar:not(.collapsed) .sidebar-agent-main > span,
.sidebar:not(.collapsed) .sidebar-agent-new,
.sidebar:not(.collapsed) .direct-session-list,
.sidebar:not(.collapsed) .group-nav-section {
transition: opacity var(--dur-base) var(--ease-standard) 80ms;
}
@starting-style {
.sidebar:not(.collapsed) .brand-button span,
.sidebar:not(.collapsed) .new-group-button span,
.sidebar:not(.collapsed) .nav-heading,
.sidebar:not(.collapsed) .sidebar-agent-location-heading,
.sidebar:not(.collapsed) .sidebar-agent-main > span,
.sidebar:not(.collapsed) .sidebar-agent-new,
.sidebar:not(.collapsed) .direct-session-list,
.sidebar:not(.collapsed) .group-nav-section {
opacity: 0;
}
}
}