From 7af80e5a6dc0e8d2ae99a273c500ac561aadfc6d Mon Sep 17 00:00:00 2001 From: agentfleet Date: Sun, 9 Aug 2026 10:36:22 +0800 Subject: [PATCH 01/23] fix(desktop): refine Author document tabs Co-Authored-By: agentfleet --- desktop/src/i18n/index.ts | 4 + .../src/styles/partials/09-canvas-tail.css | 166 +++++++++++++ desktop/src/surfaces/AuthorSurface.tsx | 234 +++++++++++++----- 3 files changed, 344 insertions(+), 60 deletions(-) diff --git a/desktop/src/i18n/index.ts b/desktop/src/i18n/index.ts index dd29c940..c93dfad6 100644 --- a/desktop/src/i18n/index.ts +++ b/desktop/src/i18n/index.ts @@ -1589,6 +1589,8 @@ const en: Dict = { 'read.showDetails': 'Show details', 'author.docTabs': 'Open documents', + 'author.tabsScrollBack': 'Scroll to earlier documents', + 'author.tabsScrollForward': 'Scroll to later documents', 'author.words_one': '{n} word', 'author.words_other': '{n} words', 'author.placeholder': 'Write in Markdown — $math$ and ```code``` supported…', @@ -3711,6 +3713,8 @@ const zh: Dict = { 'read.showDetails': '显示详情', 'author.docTabs': '打开的文档', + 'author.tabsScrollBack': '滚动到较早的文档', + 'author.tabsScrollForward': '滚动到较后的文档', 'author.words': '{n} 词', 'author.placeholder': '用 Markdown 撰写 — 支持 $数学$ 与 ```代码```…', 'author.empty': '暂无可预览内容。', diff --git a/desktop/src/styles/partials/09-canvas-tail.css b/desktop/src/styles/partials/09-canvas-tail.css index 27228508..f17288bf 100644 --- a/desktop/src/styles/partials/09-canvas-tail.css +++ b/desktop/src/styles/partials/09-canvas-tail.css @@ -1006,6 +1006,172 @@ display: flex; flex-direction: column; } +/* Author's document strip is denser than the Reader's mixed-content tabs, but + filenames must remain recognizable. Fixed-width tabs scroll instead of + collapsing into icon/close-button noise; edge controls make that overflow + discoverable without leaving a permanent scrollbar in the workbench chrome. */ +.author-tabs-shell { + flex: 0 0 38px; + min-width: 0; + display: flex; + align-items: stretch; + border-bottom: 1px solid var(--border); + background: var(--surface); +} +.author-tabs-shell .author-doc-tabs { + flex: 1; + min-width: 0; + gap: 3px; + padding: 5px 7px 0; + border-bottom: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + scroll-behavior: smooth; + overscroll-behavior-inline: contain; +} +.author-doc-tabs::-webkit-scrollbar { + display: none; +} +.author-doc-tabs .read-tabitem { + position: relative; + flex: 0 0 clamp(148px, 16vw, 208px); + min-width: 0; + height: 32px; + max-width: none; + border-color: transparent; + border-radius: 7px 7px 0 0; + transition: color 120ms var(--ease), background 120ms var(--ease), border-color 120ms var(--ease); +} +.author-doc-tabs .read-tabitem::before { + content: ''; + position: absolute; + z-index: 1; + top: 0; + left: 10px; + right: 10px; + height: 2px; + border-radius: 0 0 2px 2px; + background: var(--accent); + opacity: 0; + transform: scaleX(0.55); + transition: opacity 120ms var(--ease), transform 120ms var(--ease); +} +.author-doc-tabs .read-tabitem:hover:not(.active) { + color: var(--text); + background: var(--hover); +} +.author-doc-tabs .read-tabitem.active { + background: var(--bg); + border-color: var(--border); +} +.author-doc-tabs .read-tabitem.active::before { + opacity: 1; + transform: scaleX(1); +} +.author-doc-tabs .read-tabitem-label { + flex: 1; + gap: 7px; + min-width: 0; + padding: 0 4px 0 10px; + letter-spacing: -0.005em; +} +.author-doc-tabs .read-tabitem-kind { + color: var(--text-muted); + transition: color 120ms var(--ease); +} +.author-doc-tabs .active .read-tabitem-kind { + color: var(--accent-text); +} +.author-tab-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.author-tab-dirty { + flex: 0 0 6px; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--warn); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--warn) 13%, transparent); +} +.author-doc-tabs .read-tabitem-badge { + flex: none; + margin-left: 0; + padding: 0 4px; + line-height: 16px; + color: var(--text-secondary); + background: var(--raised); +} +.author-doc-tabs .read-tabitem-agent { + flex: 0 0 20px; + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + margin: 0 1px 0 0; + padding: 0; + border-radius: 5px; +} +.author-doc-tabs .read-tabitem-agent:hover { + background: var(--accent-strong); +} +.author-doc-tabs .read-tabitem-x { + flex: 0 0 22px; + width: 22px; + height: 22px; + margin-right: 4px; + padding: 0; + border-radius: 5px; + opacity: 0; + transition: opacity 120ms var(--ease), color 120ms var(--ease), background 120ms var(--ease); +} +.author-doc-tabs .read-tabitem:hover .read-tabitem-x, +.author-doc-tabs .read-tabitem.active .read-tabitem-x, +.author-doc-tabs .read-tabitem-x:focus-visible, +.author-doc-tabs .read-tabitem-x.danger { + opacity: 0.72; +} +.author-doc-tabs .read-tabitem-x:hover { + color: var(--text); + background: var(--hover); + opacity: 1; +} +.author-doc-tabs .read-tabitem-x.danger { + color: var(--danger); + background: color-mix(in srgb, var(--danger) 12%, transparent); +} +.author-tab-scroll { + flex: 0 0 28px; + width: 28px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + color: var(--text-secondary); + background: var(--surface); + border: 0; + cursor: pointer; +} +.author-tab-scroll.before { + border-right: 1px solid var(--border); +} +.author-tab-scroll.after { + border-left: 1px solid var(--border); +} +.author-tab-scroll:hover:not(:disabled) { + color: var(--text); + background: var(--hover); +} +.author-tab-scroll:disabled { + color: var(--text-muted); + opacity: 0.3; + cursor: default; +} .author-nav-col { flex: none; min-height: 0; diff --git a/desktop/src/surfaces/AuthorSurface.tsx b/desktop/src/surfaces/AuthorSurface.tsx index 2b8beea2..204d512b 100644 --- a/desktop/src/surfaces/AuthorSurface.tsx +++ b/desktop/src/surfaces/AuthorSurface.tsx @@ -316,10 +316,83 @@ export function AuthorSurface(): JSX.Element { // The categorized "New ▾" dropdown — one menu for every document kind (Write / // Data / Draw / Figure), the figure rows driven by the `FIGURES` registry. const [newMenu, setNewMenu] = useState(false); + const tabStripRef = useRef(null); + const [tabOverflow, setTabOverflow] = useState({ overflowing: false, before: false, after: false }); const active = docs.find((d) => d.id === activeId); const tauri = isShell(); + // Document tabs keep a useful, stable width instead of shrinking into a row + // of indistinguishable file icons. Track the scroll edges so compact previous / + // next controls appear only when the strip genuinely overflows. + const measureTabOverflow = useCallback((): void => { + const el = tabStripRef.current; + if (el === null) return; + const max = Math.max(0, el.scrollWidth - el.clientWidth); + // `scrollIntoView({ inline: 'nearest' })` may stop at the strip's 7px + // inline padding rather than literal zero; treat that optical gutter as an + // edge so a control never looks actionable when every tab is already shown. + const edgeSlack = 8; + const next = { + overflowing: max > 1, + before: el.scrollLeft > edgeSlack, + after: el.scrollLeft < max - edgeSlack, + }; + setTabOverflow((prev) => + prev.overflowing === next.overflowing && prev.before === next.before && prev.after === next.after ? prev : next, + ); + }, []); + + useEffect(() => { + const el = tabStripRef.current; + if (el === null) return; + const frame = window.requestAnimationFrame(measureTabOverflow); + const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measureTabOverflow) : null; + observer?.observe(el); + return () => { + window.cancelAnimationFrame(frame); + observer?.disconnect(); + }; + }, [docs.length, measureTabOverflow]); + + // Opening or selecting a document should never leave its tab hidden beyond + // an overflow edge. `nearest` moves the strip only as far as necessary. + useEffect(() => { + const el = tabStripRef.current; + if (el === null || activeId === null) return; + const frame = window.requestAnimationFrame(() => { + const tab = Array.from(el.children).find((child) => (child as HTMLElement).dataset.docId === activeId); + tab?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + measureTabOverflow(); + }); + return () => window.cancelAnimationFrame(frame); + }, [activeId, docs.length, measureTabOverflow]); + + function scrollTabs(direction: -1 | 1): void { + const el = tabStripRef.current; + if (el === null) return; + el.scrollBy({ left: direction * Math.max(180, el.clientWidth * 0.72), behavior: 'smooth' }); + } + + function moveTabFocus(e: React.KeyboardEvent): void { + const target = e.target as HTMLElement; + if (target.getAttribute('role') !== 'tab' || docs.length === 0) return; + const current = docs.findIndex((d) => d.id === activeId); + let next = current < 0 ? 0 : current; + if (e.key === 'ArrowRight') next = (next + 1) % docs.length; + else if (e.key === 'ArrowLeft') next = (next - 1 + docs.length) % docs.length; + else if (e.key === 'Home') next = 0; + else if (e.key === 'End') next = docs.length - 1; + else return; + e.preventDefault(); + setActive(docs[next].id); + window.requestAnimationFrame(() => { + const strip = tabStripRef.current; + const tab = strip === null ? undefined : Array.from(strip.children).find((child) => (child as HTMLElement).dataset.docId === docs[next].id); + tab?.querySelector('[role="tab"]')?.focus(); + }); + } + // The dock companion's context provider (the unified assistant dock, D2.2 — // state/companionContext.ts): the active document. Register on mount + // active-document change, unregister on unmount / no active document (the @@ -641,70 +714,111 @@ export function AuthorSurface(): JSX.Element { )}
{docs.length > 0 && ( -
- {docs.map((d) => { - const draft = d.filePath === undefined; - const openTab = (e: { clientX: number; clientY: number; preventDefault: () => void }): void => { - const items: MenuItem[] = [{ label: t('author.navRename'), onClick: () => void renameDoc(d.id) }]; - if (draft && folder !== null) { - items.push({ label: t('author.navSaveToWorkspace'), onClick: () => void saveDraftToWorkspace(d.id) }); - } - if (!draft && tauri) { - const p = d.filePath as string; - items.push({ label: t('author.fReveal'), onClick: () => revealPath(p) }); - } - items.push({ label: t('author.navClose'), danger: true, onClick: () => closeTab(d.id) }); - openTabMenu(e, items); - }; - return ( - - + )} +
+ {docs.map((d) => { + const draft = d.filePath === undefined; + const title = d.title !== '' ? d.title : t('author.untitled'); + const edit = latestAgentEdit(agentEdits, d.id); + const editTitle = edit === undefined ? '' : agentEditTitle(edit, agentEditCount(agentEdits, d.id), t); + const openTab = (e: { clientX: number; clientY: number; preventDefault: () => void }): void => { + const items: MenuItem[] = [{ label: t('author.navRename'), onClick: () => void renameDoc(d.id) }]; + if (draft && folder !== null) { + items.push({ label: t('author.navSaveToWorkspace'), onClick: () => void saveDraftToWorkspace(d.id) }); + } + if (!draft && tauri) { + const p = d.filePath as string; + items.push({ label: t('author.fReveal'), onClick: () => revealPath(p) }); + } + items.push({ label: t('author.navClose'), danger: true, onClick: () => closeTab(d.id) }); + openTabMenu(e, items); + }; + return ( + - - {d.dirty === true ? '● ' : ''} - {d.title !== '' ? d.title : t('author.untitled')} - {draft && {t('author.navDraft')}} - - {/* B6: attribution + one-click revert. Outside the tab button - (a button cannot nest one) so it is reachable without - switching to the tab — reverting a write on a document you - are not looking at is the common case when an agent works - across several. */} - {latestAgentEdit(agentEdits, d.id) !== undefined && ( - )} - - - ); - })} + {/* B6: attribution + one-click revert. Outside the tab button + (a button cannot nest one) so it is reachable without + switching to the tab — reverting a write on a document you + are not looking at is the common case when an agent works + across several. */} + {edit !== undefined && ( + + )} + + + ); + })} +
+ {tabOverflow.overflowing && ( + + )}
)} {active !== undefined ? ( From 5de5342d8a450e05c732234bd2075e2cc7441b47 Mon Sep 17 00:00:00 2001 From: agentfleet Date: Sun, 9 Aug 2026 11:19:39 +0800 Subject: [PATCH 02/23] feat(desktop): polish visual hierarchy Co-Authored-By: OpenAI Codex --- desktop/src/styles/partials/01-base-shell.css | 94 +++++++++++++++---- .../src/styles/partials/02-job-surfaces.css | 63 +++++++++---- .../styles/partials/05-transcript-boards.css | 6 +- .../styles/partials/06-settings-terminal.css | 33 ++++--- .../styles/partials/07-panels-vault-misc.css | 19 +++- desktop/src/surfaces/AttentionDock.tsx | 10 +- desktop/src/surfaces/AuditConsole.tsx | 15 ++- desktop/src/ui/ActivityBar.tsx | 4 +- desktop/src/ui/AppShell.tsx | 22 ++--- desktop/src/ui/StatusBar.tsx | 8 +- 10 files changed, 193 insertions(+), 81 deletions(-) diff --git a/desktop/src/styles/partials/01-base-shell.css b/desktop/src/styles/partials/01-base-shell.css index 50d8a13e..60095883 100644 --- a/desktop/src/styles/partials/01-base-shell.css +++ b/desktop/src/styles/partials/01-base-shell.css @@ -14,11 +14,11 @@ --pane-gap: 1px; /* Surface ladder — a tight near-black scale (Linear/Radix-grounded); 1px * hairlines carry separation, so the steps stay subtle and clean, not muddy. */ - --bg: #08090c; /* app ground */ - --canvas: #0d0f13; /* navigator + dock rails */ - --surface: #131519; /* cards, panels, popovers */ - --raised: #191c22; /* buttons, hover targets, inset controls */ - --hover: #20242b; /* row / interactive hover fill */ + --bg: #090a0d; /* app ground */ + --canvas: #0c0e12; /* navigator + dock rails */ + --surface: #111318; /* cards, panels, popovers */ + --raised: #171a20; /* buttons, hover targets, inset controls */ + --hover: #1c2027; /* row / interactive hover fill */ --input: #0b0c10; --border: #23262d; /* hairline (Radix step 6/7) */ --border-strong: #31363f; /* interactive / focus border (step 8) */ @@ -99,6 +99,14 @@ --track-tight: -0.018em; --track-snug: -0.011em; --track-body: -0.006em; + /* Control geometry. One compact ladder replaces per-component heights and + * makes toolbars, dialogs and dense workbench chrome feel related. */ + --control-sm: 28px; + --control-md: 32px; + --control-lg: 40px; + --radius-control: 6px; + --radius-card: 10px; + --radius-overlay: 14px; /* Phantom-token aliases (#318). These names were referenced across the sheet * through an inline var() fallback but never actually defined — so the fallback * was silently load-bearing (and inconsistent: one fell back to a raw #fff). @@ -127,11 +135,11 @@ } :root[data-theme='light'] { color-scheme: light; - --bg: #f7f8fa; - --canvas: #eef0f3; + --bg: #f7f7f8; + --canvas: #f1f2f4; --surface: #ffffff; --raised: #ffffff; - --hover: #e4e9f0; /* was #eef1f5 ≈1.1:1 on white — nearly invisible */ + --hover: #e9ebef; --input: #f6f7f9; --border: #e6e9ee; --border-strong: #d4d9e1; @@ -315,17 +323,22 @@ pre { display: flex; align-items: center; gap: var(--spacing-s4); + min-height: 42px; padding: var(--spacing-s4) var(--spacing-s12); background: var(--canvas); border-bottom: 1px solid var(--border); } .fleet-toolbar-label { - font-size: var(--font-size-label); - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--text-muted); + font-size: var(--font-size-body-small); + letter-spacing: var(--track-snug); + color: var(--text); font-weight: 600; } +.fleet-toolbar button { + min-height: var(--control-sm); + padding: 4px 9px; + font-size: var(--font-size-body-small); +} .fleet-toolbar-sep { width: 1px; height: 16px; @@ -342,7 +355,7 @@ pre { align-items: center; gap: var(--spacing-s16); padding: 0 var(--spacing-s16); - background: linear-gradient(180deg, var(--canvas), var(--surface)); + background: var(--canvas); border-bottom: 1px solid var(--border); } @@ -356,7 +369,7 @@ pre { height: 28px; border-top: 1px solid var(--border); border-bottom: none; - background: linear-gradient(180deg, var(--surface), var(--canvas)); + background: var(--canvas); font-size: var(--font-size-caption); font-variant-numeric: tabular-nums; color: var(--text-secondary); @@ -376,7 +389,19 @@ pre { } .statusbar-term:hover, .statusbar-term.active { - color: var(--accent-text); + color: var(--text); +} +.statusbar-term.active { + background: var(--hover); +} +.statusbar-count { + color: var(--text-secondary); +} +.statusbar-count.attention { + color: var(--warn); +} +.statusbar-hosts.quiet { + color: var(--text-muted); } /* Background-sync indicator (Author workspace + Read/Zotero library jobs). Shown @@ -417,7 +442,7 @@ pre { display: flex; gap: var(--pane-gap); min-height: 0; - background: var(--border); + background: var(--bg); } .shell-body > .navigator { flex: 0 0 auto; /* width set inline by MissionLayout (resizable) */ @@ -503,11 +528,12 @@ pre { } .region-header { + min-height: 36px; padding: var(--spacing-s8) var(--spacing-s12); - font-size: var(--font-size-caption); - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--text-muted); + font-size: var(--font-size-body-small); + font-weight: 550; + letter-spacing: var(--track-snug); + color: var(--text-secondary); position: sticky; top: 0; background: var(--canvas); @@ -519,6 +545,34 @@ pre { background: var(--canvas); } +/* Empty states are intentionally quiet and composed: a small semantic glyph, + one useful line, and no decorative card competing with real content. */ +.empty-state { + min-height: 160px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-s8); + padding: var(--spacing-s24); + color: var(--text-muted); + text-align: center; + font-size: var(--font-size-body-small); +} +.empty-state-icon { + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-stadium); + color: var(--text-secondary); + background: var(--hover); +} +.dock .empty-state { + min-height: 120px; +} + /* Foldable region header (attention dock): title on the left, fold toggle on the right. */ .region-header.foldable { diff --git a/desktop/src/styles/partials/02-job-surfaces.css b/desktop/src/styles/partials/02-job-surfaces.css index 22743c0e..d04e3bc2 100644 --- a/desktop/src/styles/partials/02-job-surfaces.css +++ b/desktop/src/styles/partials/02-job-surfaces.css @@ -4,11 +4,11 @@ .activity-bar { flex: 0 0 auto; - width: 68px; + width: 72px; display: flex; flex-direction: column; gap: var(--spacing-s2); - padding: var(--spacing-s8) var(--spacing-s4); + padding: var(--spacing-s8) var(--spacing-s6); background: var(--canvas); border-right: 1px solid var(--border); /* Visible (not auto) so the hub-switcher dropdown at the top can escape the @@ -16,13 +16,15 @@ overflow: visible; } .activity-tab { + position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px; - padding: var(--spacing-s8) var(--spacing-s2); - border: 1px solid transparent; - border-radius: var(--radius-sm); + min-height: 52px; + padding: 7px var(--spacing-s2); + border: 0; + border-radius: var(--radius-control); background: transparent; color: var(--text-secondary); cursor: pointer; @@ -33,9 +35,18 @@ color: var(--text); } .activity-tab.active { - background: var(--accent-tint); - color: var(--accent-text); - border-color: var(--accent-line); + background: var(--hover); + color: var(--text); +} +.activity-tab.active::before { + content: ''; + position: absolute; + left: -6px; + top: 14px; + width: 2px; + height: 24px; + border-radius: var(--radius-stadium); + background: var(--accent); } /* The job pinned as the split's secondary pane (S2). `position: relative` is on the modifier, not `.activity-tab`, so an unsplit rail keeps its old stacking. */ @@ -55,7 +66,7 @@ display: flex; align-items: center; justify-content: center; - height: 24px; + height: 22px; } .job-icon { display: block; @@ -102,14 +113,24 @@ left: 0; top: calc(100% + 4px); } -.activity-hub .pill.offline { - justify-content: center; - font-size: var(--font-size-label); -} -.activity-hub button.primary { +.activity-hub .hub-connect { width: 100%; - padding: 3px var(--spacing-s8); + min-height: var(--control-md); + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--spacing-s4); + padding: 3px var(--spacing-s4); font-size: var(--font-size-caption); + color: var(--text-secondary); + background: transparent; + border-color: transparent; +} +.hub-status-dot { + width: 6px; + height: 6px; + border-radius: var(--radius-stadium); + background: var(--warn); } .activity-jobs { flex: 1 1 auto; @@ -132,7 +153,10 @@ .statusbar-palette { font-variant-numeric: tabular-nums; font-size: var(--font-size-caption); + min-height: 22px; padding: 1px 7px; + color: var(--text-muted); + background: transparent; } /* The status bar's own controls stay compact so the bar keeps its 28px height. */ .statusbar .profile-switcher .switcher-pill { @@ -182,8 +206,9 @@ button > .ui-icon:not(:only-child):first-child, margin-right: 0.35em; } .activity-label { - font-size: var(--font-size-label); - letter-spacing: 0.02em; + font-size: var(--font-size-caption); + font-weight: 500; + letter-spacing: -0.005em; } /* --- shared job-surface chrome --- */ @@ -216,12 +241,12 @@ button > .ui-icon:not(:only-child):first-child, display: flex; align-items: center; gap: var(--spacing-s8); - font-weight: 600; + font-weight: 550; letter-spacing: -0.01em; } .surface-tag { font-size: var(--font-size-label); - font-weight: 700; + font-weight: 650; color: var(--accent-text); background: var(--accent-tint); border-radius: var(--radius-xs); diff --git a/desktop/src/styles/partials/05-transcript-boards.css b/desktop/src/styles/partials/05-transcript-boards.css index cd4cd821..8be826fe 100644 --- a/desktop/src/styles/partials/05-transcript-boards.css +++ b/desktop/src/styles/partials/05-transcript-boards.css @@ -1356,17 +1356,17 @@ .card { background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius-md); + border-radius: var(--radius-card); padding: var(--spacing-s12); display: flex; flex-direction: column; gap: var(--spacing-s8); box-shadow: var(--sh-1); - transition: box-shadow 0.13s var(--ease), border-color 0.13s var(--ease); + transition: border-color 0.13s var(--ease), background 0.13s var(--ease); } .card:hover { - box-shadow: var(--sh-2); border-color: var(--border-strong); + background: color-mix(in srgb, var(--surface) 94%, var(--text) 2%); } .card-head { display: flex; diff --git a/desktop/src/styles/partials/06-settings-terminal.css b/desktop/src/styles/partials/06-settings-terminal.css index 941d7572..3622ebd7 100644 --- a/desktop/src/styles/partials/06-settings-terminal.css +++ b/desktop/src/styles/partials/06-settings-terminal.css @@ -577,22 +577,20 @@ button { color: var(--text); background: var(--raised); border: 1px solid var(--border); - border-radius: var(--radius-sm); - padding: var(--spacing-s8) var(--spacing-s12); + min-height: var(--control-md); + border-radius: var(--radius-control); + padding: 6px var(--spacing-s12); cursor: pointer; box-shadow: var(--sh-1); transition: background 0.13s var(--ease), border-color 0.13s var(--ease), - box-shadow 0.13s var(--ease), transform 0.13s var(--ease); + color 0.13s var(--ease), box-shadow 0.13s var(--ease); } button:hover { background: var(--hover); border-color: var(--border-strong); - transform: translateY(-1px); - box-shadow: var(--sh-2); } button:active { - transform: translateY(0); - box-shadow: var(--sh-1); + background: color-mix(in srgb, var(--hover) 76%, var(--text) 4%); } button:disabled { opacity: 0.5; @@ -601,14 +599,24 @@ button:disabled { box-shadow: var(--sh-1); } button.primary { - background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 90%, white 10%), var(--accent-strong)); + background: var(--accent); color: var(--accent-ink); border-color: var(--accent-strong); font-weight: 600; } button.primary:hover { + background: var(--accent-strong); border-color: var(--accent-strong); - box-shadow: var(--sh-2), 0 0 18px var(--accent-line); +} +button.ghost { + color: var(--text-secondary); + background: transparent; + border-color: transparent; +} +button.ghost:hover { + color: var(--text); + background: var(--hover); + border-color: transparent; } input, @@ -618,8 +626,9 @@ textarea { color: var(--text); background: var(--input); border: 1px solid var(--border); - border-radius: var(--radius-sm); - padding: var(--spacing-s8); + min-height: var(--control-md); + border-radius: var(--radius-control); + padding: 6px var(--spacing-s8); transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease); } input:focus, @@ -665,7 +674,7 @@ th { padding: var(--spacing-s24); background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius-md); + border-radius: var(--radius-overlay); } /* The Modal dialog div carries .connect (#313); the
inside keeps submit semantics but melts into the card's flex layout (its children become the diff --git a/desktop/src/styles/partials/07-panels-vault-misc.css b/desktop/src/styles/partials/07-panels-vault-misc.css index eca77a6e..9a9ecff2 100644 --- a/desktop/src/styles/partials/07-panels-vault-misc.css +++ b/desktop/src/styles/partials/07-panels-vault-misc.css @@ -807,8 +807,8 @@ button.search-result-link:hover { * context and paints *above* an auto backdrop regardless of tree order, so * that row bleeds through the modal and steals its wheel/pointer events. */ z-index: var(--z-menu); - background: rgba(6, 8, 12, 0.55); - backdrop-filter: blur(3px) saturate(0.9); + background: color-mix(in srgb, var(--bg) 74%, transparent); + backdrop-filter: blur(5px) saturate(0.85); display: flex; justify-content: center; align-items: flex-start; @@ -831,7 +831,7 @@ button.search-result-link:hover { max-width: 90vw; background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius-md); + border-radius: var(--radius-overlay); overflow: hidden; } .palette input { @@ -842,6 +842,7 @@ button.search-result-link:hover { padding: var(--spacing-s12) var(--spacing-s16); } .palette-item { + position: relative; display: flex; align-items: center; gap: var(--spacing-s8); @@ -859,7 +860,17 @@ button.search-result-link:hover { font-variant-numeric: tabular-nums; } .palette-item.active { - background: color-mix(in srgb, var(--accent) 18%, transparent); + background: var(--hover); +} +.palette-item.active::before { + content: ''; + position: absolute; + left: 5px; + top: 8px; + bottom: 8px; + width: 2px; + border-radius: var(--radius-stadium); + background: var(--accent); } /* ---- criteria create + deliverable send-back (Phase 5 parity) ---- */ diff --git a/desktop/src/surfaces/AttentionDock.tsx b/desktop/src/surfaces/AttentionDock.tsx index f577395e..5a11915d 100644 --- a/desktop/src/surfaces/AttentionDock.tsx +++ b/desktop/src/surfaces/AttentionDock.tsx @@ -4,6 +4,7 @@ import { useAttention } from '../hub/queries'; import { obj, str, type Entity } from '../hub/types'; import { useT } from '../i18n'; import { useSession } from '../state/session'; +import { Icon } from '../ui/Icon'; function msg(err: unknown): string { return err instanceof Error ? err.message : String(err); @@ -202,7 +203,14 @@ export function AttentionDock(): JSX.Element { if (query.isLoading) return
{t('att.loading')}
; if (query.isError) return
{msg(query.error)}
; - if (items.length === 0) return
{t('att.empty')}
; + if (items.length === 0) { + return ( +
+ + {t('att.empty')} +
+ ); + } return (
diff --git a/desktop/src/surfaces/AuditConsole.tsx b/desktop/src/surfaces/AuditConsole.tsx index 64a56d6d..a948299b 100644 --- a/desktop/src/surfaces/AuditConsole.tsx +++ b/desktop/src/surfaces/AuditConsole.tsx @@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import { str, type Entity } from '../hub/types'; import { useT } from '../i18n'; import { useSession } from '../state/session'; +import { Icon } from '../ui/Icon'; function field(row: Entity, keys: string[]): string { for (const k of keys) { @@ -30,6 +31,15 @@ export function AuditConsole(): JSX.Element { } const rows = query.data ?? []; + if (rows.length === 0) { + return ( +
+ + {t('audit.empty')} +
+ ); + } + return (
@@ -50,11 +60,6 @@ export function AuditConsole(): JSX.Element { ))} - {rows.length === 0 && ( - - - - )}
{field(row, ['target', 'summary', 'ref', 'target_id'])}
{t('audit.empty')}
diff --git a/desktop/src/ui/ActivityBar.tsx b/desktop/src/ui/ActivityBar.tsx index a68dbbf0..8d353f5a 100644 --- a/desktop/src/ui/ActivityBar.tsx +++ b/desktop/src/ui/ActivityBar.tsx @@ -73,7 +73,7 @@ export function ActivityBar({ chrome }: { chrome?: ReactNode }): JSX.Element { }} > - + {t(j.labelKey)} {beside &&