Files
fusion/packages/dashboard/app/components/ChatView.css
Fusion f894bdc1d6 feat(FN-3620): wire Hermes chat message tools and document mailbox behavior
Merges FN-3620, completing the Hermes chat mailbox integration with wired message tools, locked sender/recipient contract tests, and mailbox behavior documentation, plus a small CSS polish adding `focus-visible` styles and token radius to the render toggle. A changeset for FN-3710 (cluster task ID o

Fusion-Task-Id: FN-3620
2026-05-07 16:53:55 -07:00

1267 lines
29 KiB
CSS

/* ── Chat View ─────────────────────────────────────────────────────────────── */
.chat-view {
display: flex;
height: 100%;
overflow: hidden;
}
/* Sidebar */
.chat-sidebar {
min-width: 0;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
background: var(--bg-secondary);
}
.chat-sidebar--hidden {
display: none;
}
.chat-sidebar-resize-handle {
position: relative;
width: var(--space-sm);
flex-shrink: 0;
cursor: col-resize;
background: transparent;
touch-action: none;
transition: background var(--transition-fast);
}
.chat-sidebar-resize-handle::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: var(--space-xs);
transform: translateX(-50%);
}
.chat-sidebar-resize-handle:hover::before,
.chat-sidebar-resize-handle:active::before {
background: color-mix(in srgb, var(--todo) 30%, transparent);
}
.chat-sidebar-resize-handle:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-sidebar-search-wrapper {
position: relative;
display: flex;
align-items: center;
}
.chat-sidebar-search-icon {
position: absolute;
left: var(--space-md);
color: var(--text-tertiary);
pointer-events: none;
}
.chat-sidebar-search {
width: 100%;
padding: var(--space-sm) var(--space-md) var(--space-sm) calc(var(--space-2xl) + var(--space-xs));
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg);
color: var(--text);
font-size: 13px;
}
.chat-session-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 4px 8px;
}
.chat-session-item {
padding: 10px 12px;
border-radius: var(--radius-md);
cursor: pointer;
display: flex;
flex-direction: column;
gap: 2px;
transition: background 0.15s;
position: relative;
}
.chat-session-item:hover {
background: var(--hover);
}
.chat-session-item--active {
border-left: calc(var(--btn-border-width) * 3) solid var(--todo);
padding-left: calc(var(--space-md) - (var(--btn-border-width) * 3));
background: color-mix(in srgb, var(--todo) 12%, transparent);
}
.chat-session-item--active:hover {
background: color-mix(in srgb, var(--todo) 16%, transparent);
}
.chat-session-title {
font-weight: 500;
font-size: 14px;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-session-preview {
font-size: 12px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-session-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
color: var(--text-tertiary);
}
.chat-session-meta-model {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-session-meta-model .provider-icon {
flex-shrink: 0;
}
/* === Chat Session Delete Button === */
.chat-session-delete-btn {
position: absolute;
top: var(--space-sm);
right: var(--space-sm);
display: flex;
align-items: center;
justify-content: center;
width: calc(var(--space-md) * 2);
height: calc(var(--space-md) * 2);
padding: 0;
background: transparent;
border: none;
border-radius: var(--radius-sm);
color: var(--text-muted);
cursor: pointer;
opacity: 0;
transition: opacity var(--transition-fast), color var(--transition-fast);
}
.chat-session-item:hover .chat-session-delete-btn {
opacity: 1;
}
.chat-session-delete-btn:hover {
color: var(--color-error);
opacity: 1;
}
.chat-session-delete-btn:focus {
opacity: 1;
outline: none;
box-shadow: var(--focus-ring-strong);
}
/* Context menu for session items */
.chat-session-context-menu {
position: fixed;
background: var(--bg-elevated, var(--bg));
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
padding: var(--space-xs);
z-index: 100;
}
.chat-session-context-menu button {
display: flex;
align-items: center;
gap: var(--space-sm);
width: 100%;
padding: var(--space-sm) var(--space-md);
border: none;
background: none;
color: var(--text);
cursor: pointer;
border-radius: var(--radius-sm);
font-size: 13px;
}
.chat-session-context-menu button:hover {
background: var(--hover);
}
/* Main chat thread */
.chat-thread {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
position: relative;
}
.chat-thread-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 8px;
}
.chat-thread-header-identity {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
min-width: 0;
}
.chat-thread-header-title {
font-weight: 600;
font-size: 15px;
min-width: 0;
}
.chat-thread-header-new-chat {
flex-shrink: 0;
}
/* Single thread-wide markdown / plain-text toggle, anchored to the right of
* the header next to "New Chat". Replaces the per-message eye toggle that
* used to live inside every assistant bubble. */
.chat-thread-header-render-toggle {
margin-left: auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: calc(var(--space-md) * 3);
height: calc(var(--space-md) * 3);
min-width: calc(var(--space-md) * 3);
min-height: calc(var(--space-md) * 3);
padding: 0;
border: none;
border-radius: var(--radius-sm);
color: var(--text-muted);
background: transparent;
cursor: pointer;
outline: none;
transition: background var(--transition-fast), color var(--transition-fast);
}
.chat-thread-header-render-toggle:hover {
background: var(--bg-hover, var(--bg-secondary));
color: var(--text);
}
.chat-thread-header-render-toggle:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-thread-header-render-toggle--plain {
color: var(--text);
}
/* Messages */
.chat-messages {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
/* Stop iOS rubber-band scroll from propagating to the parent /
document when the user swipes on the edge of the messages list
with the keyboard up. Without this, the bounce escapes the
scroll container, the page itself shifts, and the chat-thread
transform momentarily exposes the body background as a gray
box partially covering the messages. */
overscroll-behavior: contain;
}
.chat-message {
max-width: 75%;
padding: 10px 14px;
border-radius: 12px;
font-size: 14px;
line-height: 1.5;
word-break: break-word;
}
.chat-jump-to-latest {
position: absolute;
left: 50%;
bottom: calc(var(--space-xl) * 3);
transform: translateX(-50%);
z-index: 2;
}
.chat-message--user {
align-self: flex-end;
background: var(--accent);
color: var(--accent-text);
border-bottom-right-radius: var(--radius-sm);
}
.chat-message--assistant {
align-self: flex-start;
background: var(--bg-elevated, var(--bg-secondary));
color: var(--text);
border-bottom-left-radius: var(--radius-sm);
}
.chat-message-avatar {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
font-size: 12px;
color: var(--text-secondary);
}
/* Model-only chats hide the per-message agent identity. The toggle still
* needs a place to live, so collapse the avatar row to just the toggle on
* the right edge instead of a full identity strip. */
.chat-message-avatar.chat-message-avatar--toolbar-only {
margin-bottom: 0;
min-height: 0;
justify-content: flex-end;
}
.chat-message-render-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
width: calc(var(--space-md) * 3);
height: calc(var(--space-md) * 3);
min-width: calc(var(--space-md) * 3);
min-height: calc(var(--space-md) * 3);
padding: 0;
border: none;
color: var(--text-muted);
background: transparent;
cursor: pointer;
outline: none;
}
.chat-message--assistant .chat-message-render-toggle {
opacity: 0.6;
transition: opacity var(--transition-fast);
}
.chat-message--assistant:hover .chat-message-render-toggle,
.chat-message-render-toggle:focus-visible,
.chat-message-render-toggle.chat-message-render-toggle--plain {
opacity: 1;
}
.chat-message-render-toggle:hover {
color: var(--text);
background: color-mix(in srgb, var(--surface) 55%, transparent);
}
.chat-message-render-toggle:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-message-render-toggle--plain {
color: var(--text);
}
.chat-model-tag {
font-size: 11px;
padding: 1px 6px;
border-radius: 4px;
background: var(--surface-bg, color-mix(in srgb, var(--surface) 55%, transparent));
color: var(--text-secondary);
margin-left: 6px;
white-space: nowrap;
vertical-align: middle;
}
.chat-message-content {
white-space: pre-wrap;
}
.chat-message-content--plain {
white-space: pre-wrap;
}
.chat-message-content--markdown {
white-space: normal;
overflow-wrap: anywhere;
}
.chat-message-copy-action {
display: flex;
align-items: center;
justify-content: center;
margin-top: var(--space-xs);
margin-left: auto;
width: calc(var(--space-lg) * 2);
height: calc(var(--space-lg) * 2);
min-width: calc(var(--space-lg) * 2);
min-height: calc(var(--space-lg) * 2);
padding: 0;
border: none;
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--surface) 35%, transparent);
color: var(--text-muted);
cursor: pointer;
opacity: 0.85;
transition: opacity var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}
.chat-message-copy-action:hover {
opacity: 1;
color: var(--text);
background: color-mix(in srgb, var(--surface) 55%, transparent);
}
.chat-message-copy-action:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
opacity: 1;
}
.chat-message-copy-action--success {
color: var(--color-success);
}
.chat-message-copy-action--error {
color: var(--color-error);
}
.chat-message-content--markdown > :first-child {
margin-top: 0;
}
.chat-message-content--markdown > :last-child {
margin-bottom: 0;
}
.chat-message-content--markdown p,
.chat-message-content--markdown ul,
.chat-message-content--markdown ol,
.chat-message-content--markdown blockquote,
.chat-message-content--markdown pre,
.chat-message-content--markdown table {
margin: 0 0 var(--space-sm);
}
.chat-message-content--markdown ul,
.chat-message-content--markdown ol {
padding-left: var(--space-lg);
}
.chat-message-content--markdown code {
font-family: var(--font-mono);
font-size: 0.8125rem;
}
.chat-message-content--markdown :not(pre) > code {
padding: 0 var(--space-xs);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--surface) 55%, transparent);
}
.chat-markdown-pre {
margin: 0 0 var(--space-sm);
padding: var(--space-sm);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--surface) 65%, transparent);
overflow-x: auto;
white-space: pre;
max-width: 100%;
}
.chat-markdown-table {
display: block;
width: 100%;
max-width: 100%;
overflow-x: auto;
border-collapse: collapse;
}
.chat-markdown-table th,
.chat-markdown-table td {
border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
padding: var(--space-xs) var(--space-sm);
}
.chat-message-time {
font-size: 11px;
color: var(--text-tertiary);
margin-top: 4px;
}
.chat-message-thinking {
margin-top: 6px;
}
.chat-message-thinking summary {
font-size: 12px;
color: var(--text-secondary);
cursor: pointer;
}
.chat-message-thinking-content {
font-size: 13px;
color: var(--text-secondary);
padding: 8px;
background: var(--bg);
border-radius: 6px;
margin-top: 4px;
white-space: pre-wrap;
font-family: var(--font-mono, monospace);
overflow-x: auto;
}
/* === Chat Tool Calls === */
.chat-tool-calls {
margin-top: var(--space-sm);
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.chat-tool-calls-header {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
color: var(--text-muted);
font-size: var(--space-md);
}
.chat-tool-calls-group {
border: var(--btn-border-width, 1px) solid color-mix(in srgb, var(--border) 85%, transparent);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--surface) 35%, transparent);
}
.chat-tool-calls-group-summary {
display: flex;
align-items: center;
gap: var(--space-xs);
list-style: none;
cursor: pointer;
padding: var(--space-xs);
color: var(--text-muted);
border-radius: var(--radius-sm);
font-size: var(--space-md);
min-width: 0;
}
.chat-tool-calls-group-summary::marker {
content: "";
}
.chat-tool-calls-group-summary::-webkit-details-marker {
display: none;
}
.chat-tool-calls-group-summary:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-tool-calls-count {
white-space: nowrap;
flex-shrink: 0;
}
.chat-tool-calls-names {
color: var(--text-dim);
font-family: var(--font-mono, monospace);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 auto;
min-width: 0;
font-size: 0.6875rem;
}
.chat-tool-calls-group-status {
margin-left: auto;
font-size: 0.6875rem;
color: var(--text-dim);
flex-shrink: 0;
}
.chat-tool-calls-group > .chat-tool-call {
margin: 0 var(--space-xs) var(--space-xs);
}
.chat-tool-calls-group > .chat-tool-call:last-child {
margin-bottom: var(--space-xs);
}
.chat-tool-call {
border: var(--btn-border-width, 1px) solid color-mix(in srgb, var(--border) 85%, transparent);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--surface) 35%, transparent);
}
.chat-tool-call summary {
display: flex;
align-items: center;
gap: var(--space-xs);
list-style: none;
cursor: pointer;
padding: var(--space-xs);
color: var(--text-muted);
border-radius: var(--radius-sm);
font-size: var(--space-md);
min-width: 0;
}
.chat-tool-call summary::marker {
content: "";
}
.chat-tool-call summary::-webkit-details-marker {
display: none;
}
.chat-tool-call summary:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-tool-call-status-dot {
width: calc((var(--space-xs) + var(--space-sm)) / 2);
height: calc((var(--space-xs) + var(--space-sm)) / 2);
border-radius: 50%;
background: var(--color-success);
flex-shrink: 0;
}
.chat-tool-call-name {
font-family: var(--font-mono, monospace);
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-tool-call-preview {
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
font-size: 0.6875rem;
}
.chat-tool-call-status-text {
margin-left: auto;
font-size: 0.6875rem;
text-transform: lowercase;
}
.chat-tool-call-content {
margin: var(--space-xs);
padding: var(--space-sm);
border-radius: var(--radius-sm);
background: var(--bg);
font-family: var(--font-mono, monospace);
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.chat-tool-call-row {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-xs);
align-items: start;
}
.chat-tool-call-label {
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
font-size: 0.6875rem;
}
.chat-tool-call-value {
color: var(--text);
word-break: break-word;
white-space: pre-wrap;
}
.chat-tool-call--running .chat-tool-call-status-dot {
background: var(--color-info);
animation: tool-call-pulse var(--transition-slow) infinite;
}
.chat-tool-call--error summary {
color: var(--color-error);
}
.chat-tool-call--error .chat-tool-call-status-dot {
background: var(--color-error);
}
.chat-tool-call-row--error {
background: color-mix(in srgb, var(--color-error) 10%, transparent);
border-radius: var(--radius-sm);
padding: var(--space-xs);
}
.chat-tool-calls--compact .chat-tool-calls-header,
.chat-tool-calls--compact .chat-tool-calls-group-summary,
.chat-tool-calls--compact .chat-tool-calls-names,
.chat-tool-calls--compact .chat-tool-calls-group-status,
.chat-tool-calls--compact .chat-tool-call summary,
.chat-tool-calls--compact .chat-tool-call-content,
.chat-tool-calls--compact .chat-tool-call-preview,
.chat-tool-calls--compact .chat-tool-call-value,
.chat-tool-calls-group--compact .chat-tool-calls-group-summary,
.chat-tool-calls-group--compact .chat-tool-calls-names {
font-size: 0.6875rem;
}
@keyframes tool-call-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
/* Streaming indicator */
.chat-message--streaming {
align-self: flex-start;
background: var(--bg-elevated, var(--bg-secondary));
color: var(--text);
border-bottom-left-radius: 4px;
opacity: 0.9;
}
.chat-typing-indicator {
display: inline-flex;
gap: 4px;
padding: 4px 0;
}
.chat-typing-indicator span {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--text-secondary);
animation: chat-typing-bounce 1.4s infinite ease-in-out;
}
.chat-typing-indicator span:nth-child(1) { animation-delay: 0s; }
.chat-typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.chat-typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes chat-typing-bounce {
0%, 80%, 100% { opacity: 0.3; }
40% { opacity: 1; }
}
/* Chat waiting state */
.chat-message-content--waiting {
color: var(--text-muted);
font-style: italic;
font-size: 13px;
}
.quick-chat-panel-waiting {
color: var(--text-muted) !important;
font-style: italic;
}
/* Input area */
.chat-input-area {
position: relative;
padding: var(--space-md) var(--space-lg);
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: var(--space-sm);
/* Stop iOS rubber-band escape when the user swipes on the composer
edge with the keyboard up. Without it the page momentarily shifts
and the body background peeks through above the messages. */
overscroll-behavior: contain;
}
.chat-input-row {
display: flex;
align-items: flex-end;
gap: var(--space-sm);
width: 100%;
}
/* === Chat Skill Menu === */
.chat-skill-menu {
position: absolute;
left: var(--space-lg);
bottom: calc(100% + var(--space-xs));
min-width: calc((var(--space-xl) * 10) + var(--space-2xl) + (var(--space-xs) * 2));
max-width: calc(100% - (var(--space-lg) * 2));
max-height: calc(var(--space-xl) * 10);
overflow-y: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
z-index: 50;
}
.chat-skill-menu-item {
width: 100%;
border: none;
background: transparent;
color: inherit;
text-align: left;
padding: var(--space-sm) var(--space-md);
cursor: pointer;
display: flex;
flex-direction: column;
gap: var(--space-xs);
transition: background var(--transition-fast);
}
.chat-skill-menu-item:hover,
.chat-skill-menu-item--highlighted {
background: var(--card-hover);
}
.chat-skill-menu-item:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
background: var(--card-hover);
}
.chat-skill-menu-item-name {
font-size: 0.8125rem;
color: var(--text);
font-family: var(--font-mono);
}
.chat-skill-menu-item-description {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc((var(--space-xl) * 10) + var(--space-lg) + var(--space-xs));
}
.chat-skill-menu-empty {
padding: var(--space-sm) var(--space-md);
color: var(--text-muted);
font-size: 0.8125rem;
}
.chat-input-textarea {
flex: 1;
resize: none;
border: 1px solid var(--border);
border-radius: 12px;
padding: 10px 14px;
background: var(--bg);
color: var(--text);
font-size: 14px;
font-family: inherit;
line-height: 1.4;
max-height: 120px;
min-height: 40px;
/* touch-action: manipulation allows tap-to-focus (so iOS doesn't
auto-dismiss the keyboard between gesture frames) and blocks
double-tap zoom + pan/zoom gestures. We previously tried `none`
to lock the composer against pan, but that broke first-tap
focus on iOS. */
touch-action: manipulation;
overscroll-behavior: contain;
}
.chat-input-textarea:focus {
outline: none;
border-color: var(--accent);
}
.chat-input-send {
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: var(--accent);
color: var(--accent-text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.chat-input-send:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.chat-input-stop {
width: calc(var(--space-xl) * 2);
height: calc(var(--space-xl) * 2);
border-radius: var(--radius-md);
border: none;
background: color-mix(in srgb, var(--color-error) 15%, transparent);
color: var(--color-error);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background var(--transition-fast), transform var(--transition-fast), color var(--transition-fast);
}
.chat-input-stop:hover {
background: color-mix(in srgb, var(--color-error) 25%, transparent);
}
.chat-input-stop:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-input-stop:active {
transform: scale(0.97);
}
.chat-attach-btn {
min-width: calc(var(--space-lg) * 2);
min-height: calc(var(--space-lg) * 2);
align-self: flex-end;
}
.chat-input-wrapper {
flex: 1;
}
.chat-input-wrapper--dragover {
border: 1px dashed var(--todo);
border-radius: var(--radius-md);
}
.chat-attachment-previews {
display: flex;
gap: var(--space-xs);
padding: var(--space-sm) var(--space-md);
overflow-x: auto;
border-top: 1px solid var(--border);
background: var(--surface);
width: 100%;
}
.chat-attachment-preview {
width: calc(var(--space-xl) * 3);
height: calc(var(--space-xl) * 3);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
position: relative;
flex-shrink: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.chat-attachment-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.chat-attachment-preview-name {
font-size: 0.625rem;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-muted);
padding: var(--space-xs);
}
.chat-attachment-remove {
position: absolute;
top: calc(var(--space-xs) * -1);
right: calc(var(--space-xs) * -1);
background: color-mix(in srgb, var(--color-error) 80%, transparent);
color: var(--text);
border: none;
border-radius: var(--radius-pill);
width: calc(var(--space-md) * 2);
height: calc(var(--space-md) * 2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background var(--transition-fast), transform var(--transition-fast);
}
.chat-attachment-remove:hover {
background: color-mix(in srgb, var(--color-error) 90%, transparent);
}
.chat-attachment-remove:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.chat-attachment-remove:active {
transform: scale(0.97);
}
.chat-message-attachments {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
margin-top: var(--space-xs);
}
.chat-message-attachment-link {
display: inline-flex;
border-radius: var(--radius-sm);
}
.chat-message-attachment {
max-width: calc(var(--space-xl) * 12);
border-radius: var(--radius-sm);
}
.chat-message-attachment-file {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--surface) 50%, transparent);
font-size: 0.75rem;
color: var(--text-muted);
text-decoration: none;
}
.chat-message-attachment-file:hover {
background: color-mix(in srgb, var(--surface) 70%, transparent);
}
.chat-message-attachment-file:focus-visible,
.chat-message-attachment-link:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
/* === Chat Pending Message === */
.chat-pending-message {
display: flex;
align-items: center;
gap: var(--space-sm);
margin-top: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--todo) 10%, transparent);
color: var(--text-muted);
font-size: 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.chat-pending-message span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-pending-message-dismiss {
background: none;
border: none;
color: var(--text-dim);
cursor: pointer;
padding: var(--space-xs);
line-height: 1;
flex-shrink: 0;
font-size: 14px;
}
.chat-pending-message-dismiss:hover {
color: var(--text-muted);
}
/* Sidebar footer contains the primary New Chat action on desktop and mobile. */
.chat-sidebar-footer {
display: block;
padding: var(--space-sm) var(--space-md);
border-top: 1px solid var(--border);
background: var(--bg-secondary);
}
.chat-sidebar-footer .chat-sidebar-footer-btn {
width: 100%;
justify-content: center;
min-height: 40px;
}
/* === Mobile: one pane at a time =========================================
Sidebar (session list) and thread are siblings of .chat-view. On mobile
we show whichever one is "active" full-width: when the sidebar is
visible, the thread is hidden; when the sidebar is hidden (a session is
open), the sidebar is collapsed via .chat-sidebar--hidden and the
thread takes the full viewport. The thread already renders a back
button (ChevronLeft) on mobile to flip back to the session list. */
@media (max-width: 768px) {
.chat-sidebar {
width: 100%;
min-width: 0;
border-right: none;
}
/* When the sidebar is shown, the thread is hidden so only the session
list is visible. When the sidebar is hidden, the thread fills the
viewport. Adjacent-sibling selector keeps logic in CSS only. */
.chat-sidebar:not(.chat-sidebar--hidden) + .chat-thread {
display: none;
}
.chat-thread--keyboard-active {
height: calc(var(--vv-height, calc(100dvh - var(--keyboard-overlap, 0px))) - var(--header-height));
max-height: calc(var(--vv-height, calc(100dvh - var(--keyboard-overlap, 0px))) - var(--header-height));
/* Re-anchored: useMobileKeyboard now only updates --vv-offset-top
on resize/focus transitions (not on every visualViewport scroll
during a pan), so the transform tracks the keyboard open/close
without jittering during a swipe. */
transform: translateY(var(--vv-offset-top, 0px));
will-change: transform;
}
/* On mobile, the "New Chat" affordance uses a full-width pinned footer. */
.chat-sidebar-footer {
display: block;
padding: var(--space-sm) var(--space-md);
padding-bottom: max(var(--space-sm), env(safe-area-inset-bottom, 0));
border-top: 1px solid var(--border);
background: var(--bg-secondary);
}
.chat-sidebar-footer .chat-sidebar-footer-btn {
width: 100%;
justify-content: center;
min-height: 40px;
}
.chat-thread-header {
flex-wrap: nowrap;
}
.chat-thread-header-identity {
flex: 1 1 auto;
min-width: 0;
white-space: nowrap;
overflow: hidden;
}
.chat-thread-header-identity .chat-thread-header-title,
.chat-thread-header-identity .chat-model-tag {
overflow: hidden;
text-overflow: ellipsis;
}
.chat-thread-header-render-toggle {
flex-shrink: 0;
}
.chat-thread-header-new-chat {
display: none;
}
.chat-session-delete-btn {
opacity: 1;
width: calc(var(--space-lg) * 2.25);
height: calc(var(--space-lg) * 2.25);
min-width: calc(var(--space-lg) * 2.25);
min-height: calc(var(--space-lg) * 2.25);
}
.chat-message--assistant .chat-message-render-toggle {
opacity: 1;
}
.chat-message-copy-action {
opacity: 1;
width: calc(var(--space-lg) * 2.25);
height: calc(var(--space-lg) * 2.25);
min-width: calc(var(--space-lg) * 2.25);
min-height: calc(var(--space-lg) * 2.25);
}
.chat-tool-calls-group-summary,
.chat-tool-call summary {
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
}
.chat-tool-calls-names,
.chat-tool-call-name,
.chat-tool-call-status-text,
.chat-tool-calls-group-status,
.chat-tool-calls-count {
white-space: nowrap;
}
.chat-tool-calls-group-status,
.chat-tool-call-status-text {
margin-left: auto;
}
.chat-attach-btn,
.chat-input-send,
.chat-input-stop {
min-width: calc(var(--space-lg) * 2.25);
min-height: calc(var(--space-lg) * 2.25);
}
.chat-attachment-preview {
width: calc(var(--space-xl) * 2.5);
height: calc(var(--space-xl) * 2.5);
}
.chat-attachment-remove {
width: calc(var(--space-lg) * 2.25);
height: calc(var(--space-lg) * 2.25);
}
.chat-message {
max-width: 82%;
}
.chat-jump-to-latest {
bottom: calc(var(--space-xl) * 4);
}
}