From bfb892c985cea2bc03b08d53e7dc62c67ccd143d Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Sun, 14 Jun 2026 20:44:26 -0700 Subject: [PATCH 01/20] fix(desktop): restore zoom on timeline text via rem tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cmd +/- zoom scales the root font-size (rem-only by design), but PR #891 converted the message-timeline + thread render path from rem tokens to hardcoded px (text-[15px]/text-[13px], font-size: 15px), freezing that text against zoom. Preserve Kenny's 15px chat sizing intent but express it in rem so it scales: add rem-based `text-chat` (0.9375rem === 15px) and `text-code` (0.8125rem === 13px) Tailwind tokens, and swap the px classes over in MessageRow, markdown, mentionChip, and globals.css (.mention-highlight). Codify it: add `pnpm check:px-text` CI guard flagging new px text in the timeline/thread render path, wired into `pnpm check`, plus an AGENTS.md note steering future agents to rem tokens. Scoped to the regression footprint — no app-wide sweep. Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- AGENTS.md | 21 ++++ RESEARCH/TIMELINE_ZOOM_REM_REGRESSION.md | 45 +++++++ desktop/package.json | 3 +- desktop/scripts/check-px-text.mjs | 44 +++++++ .../src/features/messages/ui/MessageRow.tsx | 6 +- desktop/src/shared/styles/globals.css | 3 +- desktop/src/shared/ui/markdown.tsx | 6 +- desktop/src/shared/ui/mentionChip.ts | 2 +- desktop/tailwind.config.js | 9 ++ scripts/check-px-text-core.mjs | 116 ++++++++++++++++++ 10 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 RESEARCH/TIMELINE_ZOOM_REM_REGRESSION.md create mode 100644 desktop/scripts/check-px-text.mjs create mode 100644 scripts/check-px-text-core.mjs diff --git a/AGENTS.md b/AGENTS.md index 7fb96adb79..90b531ba76 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -412,6 +412,27 @@ just desktop-dev # web-only dev server (faster iteration) just dev # full Tauri app with native shell ``` +### Text sizing & zoom (use rem, never px) + +The desktop app implements Cmd +/- zoom by scaling the root `` +font-size (`desktop/src/app/useWebviewZoomShortcuts.ts`) and pinning the native +webview zoom. **Only rem-based text scales with zoom — hardcoded px text sizes +are frozen.** + +So for any readable text, reach for rem-based Tailwind tokens, never arbitrary +px: + +- ✅ `text-chat` (chat body/author, 15px in rem), `text-code` (inline/block + code, 13px in rem), or a stock token (`text-sm`, `text-base`, …). Tokens live + in `desktop/tailwind.config.js` under `theme.extend.fontSize`. +- ❌ `text-[15px]`, `text-[13px]`, or CSS `font-size: 15px`. These opted out of + zoom and caused the message-timeline regression (PR #891). + +If a design needs a size between stock tokens (e.g. 15px sits between `text-sm` +14px and `text-base` 16px), **add a rem-based token** rather than an arbitrary +px value. A CI guard (`pnpm check:px-text`, in `desktop/scripts/check-px-text.mjs`) +fails on new px text in the message-timeline / thread render path. + ### Workspace Switching The desktop app supports multiple workspaces (each backed by a different relay). diff --git a/RESEARCH/TIMELINE_ZOOM_REM_REGRESSION.md b/RESEARCH/TIMELINE_ZOOM_REM_REGRESSION.md new file mode 100644 index 0000000000..8e56cd5ffc --- /dev/null +++ b/RESEARCH/TIMELINE_ZOOM_REM_REGRESSION.md @@ -0,0 +1,45 @@ +# Timeline Zoom Regression — Findings (for Bart) + +## The bug +Cmd +/- zoom no longer scales message-timeline & thread text. + +## Why (mechanism) +`desktop/src/app/useWebviewZoomShortcuts.ts` scales the ROOT `` font-size +(rem-based scaling) and pins native `webview.setZoom(DEFAULT)`. So only **rem** +sizes scale; hardcoded **px** sizes are frozen. This is the intended approach +("only text should scale" — keeps webview coordinate system stable). Do NOT +revert to native webview zoom. + +## Two-layer history +1. **#573** (9e76a08a, May 14) — switched zoom native→root-font-size/rem-only. +2. **#891** (45f3dfe5, Jun 8, "Tune chat text sizing", klopez4212) — the recent + timeline regression. Converted timeline rem→px. + +## Kenny's intent in #891 (PRESERVE THIS) +He bumped chat text up from `text-sm` (0.875rem=14px) to **15px** because sm felt +too small. Conversions made: +- MessageRow author name `` & `

`: `text-sm` → `text-[15px]` +- markdown body / mentionChip: `text-sm` → `text-[15px]` +- `globals.css` `.mention-highlight`: added `font-size: 15px`, radius 0.375rem→4px +- also touched MessageTimeline, SystemMessageRow, MessageThreadSummaryRow + +## The crux +Tailwind v4 here uses STOCK text tokens (no `@theme` override). Stock scale: +text-sm=14px, text-base=16px. **15px sits between them — no stock token exists.** +That's WHY Kenny reached for arbitrary px. + +## tho's directive +- Use rem + Tailwind tokens wherever possible. +- Preserve Kenny's visual intent (the 15px chat sizing). +- Outcome MAY be to define/update a text-size token to yield 15px in rem — but + only if stock tokens genuinely can't deliver the look. Take a critical pass: + prefer a stock token if it looks right; introduce a custom token only if needed. + +## Scope: timeline + thread render path only +MessageRow, markdown.tsx, mentionChip.ts, SystemMessageRow, +MessageThreadSummaryRow, MessageTimeline, and the relevant globals.css rules +(incl. `.mention-highlight` px font-size). Verify zoom works after. + +## Codify (fix #2) +No custom lint exists. Add a guard (Biome rule or CI grep) flagging new +`text-[NNpx]` / px `fontSize` in the desktop app + a note in AGENTS.md/CLAUDE.md. diff --git a/desktop/package.json b/desktop/package.json index 23d76e679d..2502e25d99 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -8,8 +8,9 @@ "build": "tsc && vite build", "typecheck": "tsc --noEmit", "check:file-sizes": "node ./scripts/check-file-sizes.mjs", + "check:px-text": "node ./scripts/check-px-text.mjs", "lint": "biome lint .", - "check": "biome check . && pnpm check:file-sizes", + "check": "biome check . && pnpm check:file-sizes && pnpm check:px-text", "format": "biome format --write .", "test": "node --import ./test-loader.mjs --experimental-strip-types --test 'src/**/*.test.mjs'", "preview": "vite preview", diff --git a/desktop/scripts/check-px-text.mjs b/desktop/scripts/check-px-text.mjs new file mode 100644 index 0000000000..27693d843c --- /dev/null +++ b/desktop/scripts/check-px-text.mjs @@ -0,0 +1,44 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { runPxTextCheck } from "../../scripts/check-px-text-core.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, ".."); + +// Scoped to the message-timeline / thread render path — the surface where the +// rem→px zoom regression (PR #891) landed. Readable message text here MUST use +// rem-based tokens (`text-chat`, `text-code`) so Cmd +/- zoom scales it. We +// intentionally do NOT sweep the whole app yet (decorative chrome — avatar +// initials, day dividers, diff-viewer labels — still uses px); widen these +// roots when that sweep happens. +const rules = [ + { + root: "src/shared/ui", + extensions: new Set([".ts", ".tsx"]), + files: new Set(["markdown.tsx", "mentionChip.ts"]), + }, + { + root: "src/features/messages/ui", + extensions: new Set([".tsx"]), + files: new Set(["MessageRow.tsx"]), + }, + { + // `.mention-highlight` lives here and was part of the #891 px regression — + // guard the `font-size: NNpx` form too, not just the Tailwind utility. + root: "src/shared/styles", + extensions: new Set([".css"]), + files: new Set(["globals.css"]), + }, +]; + +// Decorative / chrome px-text exceptions: `relativePath:lineNumber`. Empty for +// now — the regression footprint is fully on rem tokens. +const overrides = new Set(); + +await runPxTextCheck({ + projectRoot, + rules, + overrides, + label: "Desktop", + scriptPath: "desktop/scripts/check-px-text.mjs", +}); diff --git a/desktop/src/features/messages/ui/MessageRow.tsx b/desktop/src/features/messages/ui/MessageRow.tsx index d54b450dd0..ca40f7dec2 100644 --- a/desktop/src/features/messages/ui/MessageRow.tsx +++ b/desktop/src/features/messages/ui/MessageRow.tsx @@ -188,7 +188,7 @@ export const MessageRow = React.memo( + {message.author} ) : ( -

+

{message.author}

); diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css index bb3fdb2492..95338f2c70 100644 --- a/desktop/src/shared/styles/globals.css +++ b/desktop/src/shared/styles/globals.css @@ -615,7 +615,8 @@ border-radius: 4px; background: hsl(var(--primary) / 0.15); padding: 3px 4px 2px; - font-size: 15px; + /* 0.9375rem === 15px; rem so it scales with the root-font-size zoom. */ + font-size: 0.9375rem; line-height: 1; color: hsl(var(--primary)); font-weight: 500; diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index b21b45e732..67ce1b63a4 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -78,7 +78,7 @@ const MAX_CACHE_ENTRIES = 100; const MAX_LOADED_LANGUAGES = 30; const MAX_HIGHLIGHT_LINES = 150; const CODE_BLOCK_CLASS = - "code-block-lines block min-w-full whitespace-pre font-mono text-[13px] leading-6 text-foreground"; + "code-block-lines block min-w-full whitespace-pre font-mono text-code text-foreground"; const DIFF_ADD_RE = /\s*\/\/\s*\[!code\s*\+\+\]\s*$/; const DIFF_REMOVE_RE = /\s*\/\/\s*\[!code\s*--\]\s*$/; @@ -850,7 +850,7 @@ function createMarkdownComponents( @@ -1221,7 +1221,7 @@ function MarkdownInner({ ].join(" ") : compact ? [ - "max-w-none break-words text-[15px] leading-6 text-foreground/90", + "max-w-none break-words text-chat text-foreground/90", "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", "[&>*+*]:mt-2", "[&>*+h1]:mt-3 [&>*+h2]:mt-3 [&>*+h3]:mt-3", diff --git a/desktop/src/shared/ui/mentionChip.ts b/desktop/src/shared/ui/mentionChip.ts index ddb6756fcc..d0c7a3502b 100644 --- a/desktop/src/shared/ui/mentionChip.ts +++ b/desktop/src/shared/ui/mentionChip.ts @@ -1,5 +1,5 @@ export const MENTION_CHIP_BASE_CLASSES = - "inline-block rounded-[4px] bg-primary/15 px-1 pt-[3px] pb-[2px] text-[15px] font-medium leading-none text-primary"; + "inline-block rounded-[4px] bg-primary/15 px-1 pt-[3px] pb-[2px] text-chat font-medium leading-none text-primary"; export const MENTION_CHIP_HOVER_CLASSES = "transition-colors hover:bg-primary/25 hover:text-primary/90"; diff --git a/desktop/tailwind.config.js b/desktop/tailwind.config.js index 6422fc81ac..93b7d8fe65 100644 --- a/desktop/tailwind.config.js +++ b/desktop/tailwind.config.js @@ -2,6 +2,15 @@ export default { theme: { extend: { + fontSize: { + // Chat body/author sizing. 15px sits between Tailwind's stock + // `text-sm` (14px) and `text-base` (16px), so we express it as a rem + // token instead of a hardcoded px value — px would not scale with the + // root-font-size zoom (Cmd +/-). 0.9375rem === 15px at the 16px root. + chat: ["0.9375rem", { lineHeight: "1.5rem" }], + // Inline & block code inside chat messages (13px → 0.8125rem). + code: ["0.8125rem", { lineHeight: "1.5rem" }], + }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", diff --git a/scripts/check-px-text-core.mjs b/scripts/check-px-text-core.mjs new file mode 100644 index 0000000000..053c6b976b --- /dev/null +++ b/scripts/check-px-text-core.mjs @@ -0,0 +1,116 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; + +/** + * Shared "no hardcoded px text size" guard. + * + * Zoom (Cmd +/-) scales the root font-size, so only **rem**-based text + * scales. Hardcoded px text sizes (`text-[15px]`, `font-size: 15px`) freeze + * against zoom — that's the timeline regression we fixed. This guard stops new + * px text sizes from creeping back in. Use a rem-based Tailwind token instead + * (e.g. `text-chat`, `text-code`, `text-sm`). + * + * It flags: + * - Tailwind arbitrary px text utilities: `text-[NNpx]` + * - CSS px font sizes: `font-size: NNpx` + * + * Decorative/chrome exceptions (avatar initials sized to a fixed avatar box, + * etc.) live in the `overrides` allowlist supplied by each app. + */ + +const TEXT_PX_RE = /\btext-\[\d+(?:\.\d+)?px\]/g; +// Match the CSS `font-size` property, but NOT custom properties like +// `--font-size:` (third-party widget vars) which merely contain the substring. +const FONT_SIZE_PX_RE = /(? { + const fullPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + return walkFiles(fullPath); + } + return [fullPath]; + }), + ); + return files.flat(); +} + +/** + * @param {object} options + * @param {string} options.projectRoot Absolute path the rule roots resolve against. + * @param {Array<{root: string, extensions: Set}>} options.rules Where to scan. + * @param {string} options.label Human label for the failure header. + * @param {Set} [options.overrides] Allowlisted "relativePath:lineNumber" entries. + * @param {string} options.scriptPath Path mentioned in the failure hint. + */ +export async function runPxTextCheck({ + projectRoot, + rules, + label, + overrides = new Set(), + scriptPath, +}) { + const candidateFiles = ( + await Promise.all( + rules.map((rule) => { + const dir = path.join(projectRoot, rule.root); + return fs + .access(dir) + .then(() => walkFiles(dir)) + .catch(() => []); + }), + ) + ).flat(); + + const violations = []; + + for (const filePath of candidateFiles) { + const relativePath = path.relative(projectRoot, filePath); + const rule = rules.find((r) => + relativePath.startsWith(`${r.root}${path.sep}`), + ); + if (!rule) { + continue; + } + if (!rule.extensions.has(path.extname(relativePath))) { + continue; + } + // Optional per-rule basename allowlist — scopes the scan to specific files. + if (rule.files && !rule.files.has(path.basename(relativePath))) { + continue; + } + + const content = await fs.readFile(filePath, "utf8"); + const lines = content.split(/\r?\n/); + lines.forEach((line, index) => { + const lineNumber = index + 1; + const key = `${relativePath}:${lineNumber}`; + if (overrides.has(key)) { + return; + } + const matches = [ + ...(line.match(TEXT_PX_RE) ?? []), + ...(line.match(FONT_SIZE_PX_RE) ?? []), + ]; + for (const match of matches) { + violations.push({ relativePath, lineNumber, match }); + } + }); + } + + if (violations.length > 0) { + console.error(`${label} px-text check failed:`); + for (const v of violations) { + console.error(`- ${v.relativePath}:${v.lineNumber}: ${v.match}`); + } + console.error( + "Use a rem-based Tailwind text token (e.g. `text-chat`, `text-code`, " + + "`text-sm`) so the text scales with Cmd +/- zoom. If this px size is " + + "genuinely decorative/chrome (not readable message text), add a " + + `narrowly scoped \`relativePath:lineNumber\` exception in \`${scriptPath}\`.`, + ); + process.exit(1); + } +} From 485d10e0d63b2232eadf0337b455c29bc98f1dfc Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Sun, 14 Jun 2026 22:38:51 -0700 Subject: [PATCH 02/20] feat(desktop): make chat text the 16px base type scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tho's reframe: chat text is the app's fundamental building block, so it should *be* the base type size, not a between-the-cracks 15px token wedged between text-sm (14px) and text-base (16px). Bump chat to 16px-as-base and re-anchor the timeline/thread type ramp: - Retire the custom `text-chat` (15px) and `text-code` (13px) rem tokens added in the zoom fix (#1051) — with chat at the stock base they're redundant artifacts of the 15px-wedge era. - Chat body + author → stock `text-base` (16px) in MessageRow, markdown, mentionChip. `.mention-highlight` follows chat to 1rem. - Code (inline + block) → stock `text-sm` (14px) — a deliberate, documented one-notch step down from the 16px base. Satellite elements (timestamps text-xs, system rows text-sm/xs, thread summary, reactions) are deliberately left on their stock tokens: against a 16px base the ratios tighten into the canonical base→sm→xs ramp (16/14/12) instead of the awkward 12/15 they sat at before. Bumping them would inflate the UI for no design gain. Everything stays rem so Cmd +/- zoom keeps scaling it; the check:px-text guard stays green (its guidance updated to point at the stock scale). Net: zero custom font tokens, timeline/thread render path only — no app-wide sweep. Composer chrome (.tiptap pre code) left untouched. Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- AGENTS.md | 17 ++-- RESEARCH/CHAT_BASE_TYPE_SCALE.md | 84 +++++++++++++++++++ desktop/scripts/check-px-text.mjs | 8 +- .../src/features/messages/ui/MessageRow.tsx | 6 +- desktop/src/shared/styles/globals.css | 4 +- desktop/src/shared/ui/markdown.tsx | 6 +- desktop/src/shared/ui/mentionChip.ts | 2 +- desktop/tailwind.config.js | 9 -- scripts/check-px-text-core.mjs | 7 +- 9 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 RESEARCH/CHAT_BASE_TYPE_SCALE.md diff --git a/AGENTS.md b/AGENTS.md index 90b531ba76..1cda517b62 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -422,16 +422,19 @@ are frozen.** So for any readable text, reach for rem-based Tailwind tokens, never arbitrary px: -- ✅ `text-chat` (chat body/author, 15px in rem), `text-code` (inline/block - code, 13px in rem), or a stock token (`text-sm`, `text-base`, …). Tokens live - in `desktop/tailwind.config.js` under `theme.extend.fontSize`. +- ✅ Stock rem tokens (`text-base`, `text-sm`, `text-xs`, …). **Chat body/author + text === `text-base` (16px) — chat is the app's base type size**, and the + surrounding timeline elements (timestamps, system rows, code, reactions) are + deliberate steps on that same stock ramp. - ❌ `text-[15px]`, `text-[13px]`, or CSS `font-size: 15px`. These opted out of zoom and caused the message-timeline regression (PR #891). -If a design needs a size between stock tokens (e.g. 15px sits between `text-sm` -14px and `text-base` 16px), **add a rem-based token** rather than an arbitrary -px value. A CI guard (`pnpm check:px-text`, in `desktop/scripts/check-px-text.mjs`) -fails on new px text in the message-timeline / thread render path. +Prefer stock tokens — they're rem and zoom-safe. Only if a design genuinely +needs a size the stock scale can't express should you **add a rem-based token** +(in `desktop/tailwind.config.js` under `theme.extend.fontSize`) rather than an +arbitrary px value. A CI guard (`pnpm check:px-text`, in +`desktop/scripts/check-px-text.mjs`) fails on new px text in the +message-timeline / thread render path. ### Workspace Switching diff --git a/RESEARCH/CHAT_BASE_TYPE_SCALE.md b/RESEARCH/CHAT_BASE_TYPE_SCALE.md new file mode 100644 index 0000000000..e71c7eca9a --- /dev/null +++ b/RESEARCH/CHAT_BASE_TYPE_SCALE.md @@ -0,0 +1,84 @@ +--- +title: "Chat-as-base type scale: re-anchoring the timeline type ramp" +tags: [desktop, typography, design-tokens, tailwind, zoom] +status: active +created: 2026-06-15 +--- + +# Chat-as-base type scale + +## Why this exists +tho's reframe (2026-06-15): **chat text *is* the base size** — the fundamental +building block of the app. It should not be a special between-the-cracks token +(`text-chat` = 15px, wedged between Tailwind's `text-sm` 14px and `text-base` +16px). Direction: **bump chat to 16px and make it the base**, then re-anchor the +*other* timeline/thread elements that currently sit relative to the old ~15px +chat assumption. This is a **deliberate, wider pass** — a design decision, not a +surgical bugfix. + +Builds on the zoom-regression work (PR #1051, branch `tho/rem-font-zoom-fix`) +which introduced the `text-chat`/`text-code` rem tokens. That fix preserved +Kenny's 15px intent; this pass *changes* that intent to 16px-as-base. + +## Non-negotiables (carry forward from the zoom fix) +- **Everything stays rem.** Cmd +/- zoom scales the root `` font-size + (rem-only by design, from #573). px would freeze against zoom — that was the + original bug. The `check:px-text` guard already enforces this; keep it happy. +- No `html { font-size }` override exists, so root is browser-default 16px and + rem math is standard (1rem = 16px). The `--font-size: 14px` var in globals.css + is scoped to the emoji-picker component, NOT the app root. + +## Current type landscape (timeline/thread render path) +`text-chat` (15px) is used in: +- `MessageRow.tsx` — markdown body wrapper, author name (`` and `

`) +- `markdown.tsx` — chat body wrapper +- `mentionChip.ts` — mention chip text +- (`text-code` 13px — inline/block code in `markdown.tsx`) +- `globals.css` `.mention-highlight` — 0.9375rem + +Satellite elements currently on the **stock** scale (these are the "other +elements that sit relative to chat" tho means — review each for whether it still +reads right once chat = 16px base): +- `MessageTimestamp.tsx` — text-xs +- `SystemMessageRow.tsx` — text-xs / text-sm +- `MessageThreadSummaryRow.tsx` — text-xs +- `MessageThreadPanel.tsx` — text-sm / text-xs +- `MessageReactions.tsx` — text-xs / text-sm +- `MessageActionBar.tsx` — text-xs +- `TypingIndicatorRow.tsx` — text-xs / text-sm +- `MessageTimeline.tsx` — text-base / text-sm / text-xl (empty states / headers) +- `MessageRow.tsx` (secondary bits) — text-sm / text-xs + +## The design question for the build +If chat === base (16px), the type ramp around it should be *intentional*, not +incidental. The relationships that matter: +- chat body / author = base (16px) +- timestamps, metadata, system rows, reactions = one or two steps down, + consistently (don't leave them as ad-hoc text-xs/text-sm if the ratio now + looks off against a 16px anchor) +- code = its own deliberate step down from chat + +Open for Bart to decide the cleanest expression: lean on stock Tailwind tokens +where they land right now that base is the anchor, retire/rename `text-chat` if +it's redundant with `text-base`, and only define custom rem tokens where the +stock scale genuinely can't express the intended step. + +## Out of scope +- App-wide px sweep (the ~130 px classes elsewhere). Stay in the + timeline/thread render path + the type tokens that serve it. +- Don't widen the `check:px-text` guard roots in this pass (Marge's noted + follow-up) unless a new render-path file demands it. + +## Decision (built) +Chat === `text-base` (16px, stock). Both custom tokens **retired** — `text-chat` +and `text-code` were artifacts of the 15px-wedge era; with chat at the stock +base they're redundant. Code (inline + block) re-anchored to stock `text-sm` +(14px) — a deliberate, documented one-notch step down from the 16px base. + +Satellites left on their stock tokens **deliberately**: against a 16px base the +ratios actually tighten into the standard ramp (e.g. timestamps `text-xs` 12px = +clean base→sm→xs drop, was the awkward 12/15 before). Bumping them would inflate +the UI for no design gain — the right call is to keep them and document why. + +Net: zero custom font tokens. `.mention-highlight` (composer) follows chat to +`1rem` (16px). Everything rem, guard stays green. diff --git a/desktop/scripts/check-px-text.mjs b/desktop/scripts/check-px-text.mjs index 27693d843c..519ec7815d 100644 --- a/desktop/scripts/check-px-text.mjs +++ b/desktop/scripts/check-px-text.mjs @@ -7,10 +7,10 @@ const projectRoot = path.resolve(__dirname, ".."); // Scoped to the message-timeline / thread render path — the surface where the // rem→px zoom regression (PR #891) landed. Readable message text here MUST use -// rem-based tokens (`text-chat`, `text-code`) so Cmd +/- zoom scales it. We -// intentionally do NOT sweep the whole app yet (decorative chrome — avatar -// initials, day dividers, diff-viewer labels — still uses px); widen these -// roots when that sweep happens. +// rem-based tokens (the stock `text-base` / `text-sm` scale, chat === base) +// so Cmd +/- zoom scales it. We intentionally do NOT sweep the whole app yet +// (decorative chrome — avatar initials, day dividers, diff-viewer labels — +// still uses px); widen these roots when that sweep happens. const rules = [ { root: "src/shared/ui", diff --git a/desktop/src/features/messages/ui/MessageRow.tsx b/desktop/src/features/messages/ui/MessageRow.tsx index ca40f7dec2..424410cdc3 100644 --- a/desktop/src/features/messages/ui/MessageRow.tsx +++ b/desktop/src/features/messages/ui/MessageRow.tsx @@ -188,7 +188,7 @@ export const MessageRow = React.memo( + {message.author} ) : ( -

+

{message.author}

); diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css index 95338f2c70..69961f3fb8 100644 --- a/desktop/src/shared/styles/globals.css +++ b/desktop/src/shared/styles/globals.css @@ -615,8 +615,8 @@ border-radius: 4px; background: hsl(var(--primary) / 0.15); padding: 3px 4px 2px; - /* 0.9375rem === 15px; rem so it scales with the root-font-size zoom. */ - font-size: 0.9375rem; + /* 1rem === 16px (chat base); rem so it scales with the root-font-size zoom. */ + font-size: 1rem; line-height: 1; color: hsl(var(--primary)); font-weight: 500; diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index 67ce1b63a4..ab298d5817 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -78,7 +78,7 @@ const MAX_CACHE_ENTRIES = 100; const MAX_LOADED_LANGUAGES = 30; const MAX_HIGHLIGHT_LINES = 150; const CODE_BLOCK_CLASS = - "code-block-lines block min-w-full whitespace-pre font-mono text-code text-foreground"; + "code-block-lines block min-w-full whitespace-pre font-mono text-sm text-foreground"; const DIFF_ADD_RE = /\s*\/\/\s*\[!code\s*\+\+\]\s*$/; const DIFF_REMOVE_RE = /\s*\/\/\s*\[!code\s*--\]\s*$/; @@ -850,7 +850,7 @@ function createMarkdownComponents( @@ -1221,7 +1221,7 @@ function MarkdownInner({ ].join(" ") : compact ? [ - "max-w-none break-words text-chat text-foreground/90", + "max-w-none break-words text-base text-foreground/90", "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", "[&>*+*]:mt-2", "[&>*+h1]:mt-3 [&>*+h2]:mt-3 [&>*+h3]:mt-3", diff --git a/desktop/src/shared/ui/mentionChip.ts b/desktop/src/shared/ui/mentionChip.ts index d0c7a3502b..583ee3ff3d 100644 --- a/desktop/src/shared/ui/mentionChip.ts +++ b/desktop/src/shared/ui/mentionChip.ts @@ -1,5 +1,5 @@ export const MENTION_CHIP_BASE_CLASSES = - "inline-block rounded-[4px] bg-primary/15 px-1 pt-[3px] pb-[2px] text-chat font-medium leading-none text-primary"; + "inline-block rounded-[4px] bg-primary/15 px-1 pt-[3px] pb-[2px] text-base font-medium leading-none text-primary"; export const MENTION_CHIP_HOVER_CLASSES = "transition-colors hover:bg-primary/25 hover:text-primary/90"; diff --git a/desktop/tailwind.config.js b/desktop/tailwind.config.js index 93b7d8fe65..6422fc81ac 100644 --- a/desktop/tailwind.config.js +++ b/desktop/tailwind.config.js @@ -2,15 +2,6 @@ export default { theme: { extend: { - fontSize: { - // Chat body/author sizing. 15px sits between Tailwind's stock - // `text-sm` (14px) and `text-base` (16px), so we express it as a rem - // token instead of a hardcoded px value — px would not scale with the - // root-font-size zoom (Cmd +/-). 0.9375rem === 15px at the 16px root. - chat: ["0.9375rem", { lineHeight: "1.5rem" }], - // Inline & block code inside chat messages (13px → 0.8125rem). - code: ["0.8125rem", { lineHeight: "1.5rem" }], - }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", diff --git a/scripts/check-px-text-core.mjs b/scripts/check-px-text-core.mjs index 053c6b976b..16e7dd6e65 100644 --- a/scripts/check-px-text-core.mjs +++ b/scripts/check-px-text-core.mjs @@ -8,7 +8,7 @@ import path from "node:path"; * scales. Hardcoded px text sizes (`text-[15px]`, `font-size: 15px`) freeze * against zoom — that's the timeline regression we fixed. This guard stops new * px text sizes from creeping back in. Use a rem-based Tailwind token instead - * (e.g. `text-chat`, `text-code`, `text-sm`). + * (e.g. the stock `text-base`, `text-sm`, `text-xs` scale — chat === base). * * It flags: * - Tailwind arbitrary px text utilities: `text-[NNpx]` @@ -106,8 +106,9 @@ export async function runPxTextCheck({ console.error(`- ${v.relativePath}:${v.lineNumber}: ${v.match}`); } console.error( - "Use a rem-based Tailwind text token (e.g. `text-chat`, `text-code`, " + - "`text-sm`) so the text scales with Cmd +/- zoom. If this px size is " + + "Use a rem-based Tailwind text token (e.g. the stock `text-base`, " + + "`text-sm`, `text-xs` scale) so the text scales with Cmd +/- zoom. " + + "If this px size is " + "genuinely decorative/chrome (not readable message text), add a " + `narrowly scoped \`relativePath:lineNumber\` exception in \`${scriptPath}\`.`, ); From c439255c69ce292f2a54fab8221092c6ad25126d Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Sun, 14 Jun 2026 23:19:57 -0700 Subject: [PATCH 03/20] fix(desktop): convert px text literals to rem so zoom scales app-wide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cmd +/- zoom scales the root html font-size (rem-only by design), but text-[Npx] font literals froze text in pixels and broke zoom outside the timeline. Convert the 63 non-overlapping UI surfaces to rem so text scales proportionally everywhere. Mapping: 12px -> stock text-xs token; sizes with no clean stock home (7-11px micro-text on avatars, badges, chips) -> 1:1 rem literals to preserve exact visual hierarchy with zero pixel shift. Also converts the 2 stray leading-[100px] line-height literals to rem. Deliberately defers the 3 render-path files (MessageRow.tsx, markdown.tsx, mentionChip.ts) to PR #1052, which already converts them and is awaiting merge — touching them here on a main base would manufacture a conflict. The check:px-text guard lands with #1052; once it merges, the guard covers this branch's 63 files plus #1052's 3. Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../src/features/agent-memory/ui/MemorySection.tsx | 4 ++-- .../features/agents/ui/AddTeamToChannelDialog.tsx | 2 +- .../features/agents/ui/AgentSessionToolItem.tsx | 6 +++--- .../agents/ui/AgentSessionTranscriptList.tsx | 6 +++--- .../features/agents/ui/ManagedAgentLogPanel.tsx | 4 ++-- desktop/src/features/agents/ui/ManagedAgentRow.tsx | 4 ++-- desktop/src/features/agents/ui/ModelPicker.tsx | 2 +- .../agents/ui/PersonaCatalogSelectionBadge.tsx | 2 +- desktop/src/features/agents/ui/RawEventRail.tsx | 4 ++-- .../features/agents/ui/RelayDirectorySection.tsx | 4 ++-- desktop/src/features/agents/ui/RespondToField.tsx | 4 ++-- desktop/src/features/agents/ui/TeamDialog.tsx | 2 +- desktop/src/features/agents/ui/TeamsSection.tsx | 6 +++--- .../channels/ui/AddChannelBotPersonasSection.tsx | 12 ++++++------ .../channels/ui/AddChannelBotTeamsSection.tsx | 8 ++++---- .../channels/ui/AgentSessionThreadPanel.tsx | 4 ++-- .../src/features/channels/ui/BotActivityBar.tsx | 8 ++++---- .../channels/ui/ChannelMemberInviteCard.tsx | 4 ++-- .../features/channels/ui/ChannelScreenHeader.tsx | 2 +- .../src/features/channels/ui/MembersSidebar.tsx | 6 +++--- .../channels/ui/MembersSidebarMemberCard.tsx | 4 ++-- desktop/src/features/channels/ui/QuickBotBar.tsx | 2 +- desktop/src/features/home/ui/FeedSection.tsx | 10 +++++----- desktop/src/features/home/ui/InboxDetailPane.tsx | 2 +- desktop/src/features/home/ui/InboxListPane.tsx | 6 +++--- .../src/features/home/ui/RecentNotesSection.tsx | 4 ++-- .../src/features/huddle/components/HuddleBar.tsx | 2 +- .../features/huddle/components/HuddleIndicator.tsx | 2 +- .../src/features/huddle/components/MicControls.tsx | 2 +- .../features/huddle/components/ParticipantList.tsx | 6 +++--- .../features/messages/ui/ComposerAttachments.tsx | 2 +- desktop/src/features/messages/ui/DayDivider.tsx | 2 +- desktop/src/features/messages/ui/DiffViewer.tsx | 6 +++--- .../src/features/messages/ui/MessageComposer.tsx | 4 ++-- .../features/messages/ui/MessageThreadPanel.tsx | 2 +- .../messages/ui/MessageThreadSummaryRow.tsx | 2 +- .../src/features/messages/ui/MessageTimeline.tsx | 2 +- .../src/features/messages/ui/SystemMessageRow.tsx | 6 +++--- .../features/messages/ui/TypingIndicatorRow.tsx | 4 ++-- desktop/src/features/onboarding/ui/AvatarStep.tsx | 2 +- .../features/onboarding/ui/MembershipDenied.tsx | 2 +- .../features/onboarding/ui/NostrKeyImportForm.tsx | 2 +- .../src/features/profile/ui/UserProfilePopover.tsx | 2 +- .../src/features/pulse/ui/AgentActivityCard.tsx | 4 ++-- desktop/src/features/pulse/ui/NoteCard.tsx | 2 +- desktop/src/features/pulse/ui/PulseTabBar.tsx | 2 +- desktop/src/features/search/ui/TopbarSearch.tsx | 6 +++--- .../settings/ui/ChannelTemplatesSettingsCard.tsx | 7 +++++-- .../features/settings/ui/DoctorSettingsPanel.tsx | 14 +++++++------- .../settings/ui/NotificationSettingsCard.tsx | 2 +- .../features/settings/ui/ProfileSettingsCard.tsx | 2 +- desktop/src/features/settings/ui/SoundPicker.tsx | 2 +- desktop/src/features/sidebar/ui/AppSidebar.tsx | 4 ++-- .../src/features/sidebar/ui/MoreUnreadButton.tsx | 2 +- .../features/sidebar/ui/NewDirectMessageDialog.tsx | 2 +- .../src/features/sidebar/ui/SidebarProfileCard.tsx | 5 ++++- desktop/src/features/sidebar/ui/SidebarSection.tsx | 8 ++++---- .../features/user-status/ui/SetStatusDialog.tsx | 2 +- desktop/src/features/workflows/ui/WorkflowCard.tsx | 2 +- .../features/workflows/ui/WorkflowDetailPanel.tsx | 6 +++--- .../src/features/workflows/ui/WorkflowRunTrace.tsx | 6 +++--- .../features/workspaces/ui/WorkspaceSwitcher.tsx | 2 +- desktop/src/shared/ui/UserAvatar.tsx | 4 ++-- desktop/src/shared/ui/VideoPlayer.tsx | 14 +++++++------- desktop/src/shared/ui/badge.tsx | 2 +- 65 files changed, 139 insertions(+), 133 deletions(-) diff --git a/desktop/src/features/agent-memory/ui/MemorySection.tsx b/desktop/src/features/agent-memory/ui/MemorySection.tsx index 0b2d4f4a45..df8025159b 100644 --- a/desktop/src/features/agent-memory/ui/MemorySection.tsx +++ b/desktop/src/features/agent-memory/ui/MemorySection.tsx @@ -253,8 +253,8 @@ function MemoryGraphView({ className="text-xs italic text-muted-foreground" data-testid="agent-memory-no-core" > - No core memory yet — - agent identity is unrooted. + No core memory yet + — agent identity is unrooted.

) : null} diff --git a/desktop/src/features/agents/ui/AddTeamToChannelDialog.tsx b/desktop/src/features/agents/ui/AddTeamToChannelDialog.tsx index c54c3e6e64..693c0e5cf0 100644 --- a/desktop/src/features/agents/ui/AddTeamToChannelDialog.tsx +++ b/desktop/src/features/agents/ui/AddTeamToChannelDialog.tsx @@ -178,7 +178,7 @@ export function AddTeamToChannelDialog({ > diff --git a/desktop/src/features/agents/ui/AgentSessionToolItem.tsx b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx index 09411f2e66..4c443ba62a 100644 --- a/desktop/src/features/agents/ui/AgentSessionToolItem.tsx +++ b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx @@ -198,7 +198,7 @@ function ToolTimestamp({ return ( - + {time} {duration ? ` · ${duration}` : null} @@ -260,7 +260,7 @@ function BuzzToolInlineAction({ if (action.onClick) { return (

{archived.length} diff --git a/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx b/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx index 69974cb30f..4cb083c30b 100644 --- a/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx +++ b/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx @@ -119,7 +119,7 @@ export function MembersSidebarMemberCard({
@@ -159,7 +159,7 @@ export function MembersSidebarMemberCard({ ) : null}
-

+

{truncatePubkey(member.pubkey)}

diff --git a/desktop/src/features/channels/ui/QuickBotBar.tsx b/desktop/src/features/channels/ui/QuickBotBar.tsx index b4eec62c0f..572811a26c 100644 --- a/desktop/src/features/channels/ui/QuickBotBar.tsx +++ b/desktop/src/features/channels/ui/QuickBotBar.tsx @@ -75,7 +75,7 @@ export function QuickBotBar({ personas, pending, onAdd }: QuickBotBarProps) { src={rewriteRelayUrl(persona.avatarUrl)} /> ) : ( - + {initials} )} diff --git a/desktop/src/features/home/ui/FeedSection.tsx b/desktop/src/features/home/ui/FeedSection.tsx index 51d871c32b..a42d3d674a 100644 --- a/desktop/src/features/home/ui/FeedSection.tsx +++ b/desktop/src/features/home/ui/FeedSection.tsx @@ -187,11 +187,11 @@ export function FeedSection({
{feedHeadline(item)} - + {item.channelName ? ( - + #{item.channelName} ) : null} - + {formatRelativeTime(item.createdAt)}
{isThreadContextLoading ? ( -
+
Loading context...
) : null} diff --git a/desktop/src/features/home/ui/InboxListPane.tsx b/desktop/src/features/home/ui/InboxListPane.tsx index f3f2af6621..b45575c604 100644 --- a/desktop/src/features/home/ui/InboxListPane.tsx +++ b/desktop/src/features/home/ui/InboxListPane.tsx @@ -67,7 +67,7 @@ export function InboxListPane({
diff --git a/desktop/src/features/messages/ui/TypingIndicatorRow.tsx b/desktop/src/features/messages/ui/TypingIndicatorRow.tsx index bd68bb042a..b0bfb01981 100644 --- a/desktop/src/features/messages/ui/TypingIndicatorRow.tsx +++ b/desktop/src/features/messages/ui/TypingIndicatorRow.tsx @@ -113,8 +113,8 @@ export function TypingIndicatorRow({ label={label} className={cn( isActivityVariant - ? "h-[18px] w-[18px] text-[7px]" - : "h-5 w-5 text-[8px]", + ? "h-[18px] w-[18px] text-[0.4375rem]" + : "h-5 w-5 text-[0.5rem]", )} iconClassName={ isActivityVariant ? "h-2.5 w-2.5" : "h-3 w-3" diff --git a/desktop/src/features/onboarding/ui/AvatarStep.tsx b/desktop/src/features/onboarding/ui/AvatarStep.tsx index a99c69013e..591b338a69 100644 --- a/desktop/src/features/onboarding/ui/AvatarStep.tsx +++ b/desktop/src/features/onboarding/ui/AvatarStep.tsx @@ -86,7 +86,7 @@ function AvatarPreview({ > 0 && "buzz-avatar-squish", )} data-testid="onboarding-avatar-preview-emoji" diff --git a/desktop/src/features/onboarding/ui/MembershipDenied.tsx b/desktop/src/features/onboarding/ui/MembershipDenied.tsx index 4f0618f94e..1a20572f3d 100644 --- a/desktop/src/features/onboarding/ui/MembershipDenied.tsx +++ b/desktop/src/features/onboarding/ui/MembershipDenied.tsx @@ -177,7 +177,7 @@ export function MembershipDenied({

This will use this Nostr identity:

-

+

{shortenNpub(previewNpub)}

diff --git a/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx b/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx index b251fb9e3b..cbff60e55b 100644 --- a/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx +++ b/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx @@ -227,7 +227,7 @@ export function NostrKeyImportForm({

This will use this Nostr identity:

-

+

{shortenNpub(previewNpub)}

diff --git a/desktop/src/features/profile/ui/UserProfilePopover.tsx b/desktop/src/features/profile/ui/UserProfilePopover.tsx index d750fa1053..9350b98b94 100644 --- a/desktop/src/features/profile/ui/UserProfilePopover.tsx +++ b/desktop/src/features/profile/ui/UserProfilePopover.tsx @@ -216,7 +216,7 @@ export function UserProfilePopover({

) : null} {profile?.displayName ? ( -

+

{truncatePubkey(pubkey)}

) : null} diff --git a/desktop/src/features/pulse/ui/AgentActivityCard.tsx b/desktop/src/features/pulse/ui/AgentActivityCard.tsx index 7fdc109775..fc11f6554e 100644 --- a/desktop/src/features/pulse/ui/AgentActivityCard.tsx +++ b/desktop/src/features/pulse/ui/AgentActivityCard.tsx @@ -75,7 +75,7 @@ export function AgentActivityCard({ {displayName}
{agentStatus ? : null} - + {formatRelativeTime(group.latestAt)} @@ -113,7 +113,7 @@ export function AgentActivityCard({
-

+

{formatRelativeTime(note.createdAt)}

diff --git a/desktop/src/features/pulse/ui/NoteCard.tsx b/desktop/src/features/pulse/ui/NoteCard.tsx index a3e71f89fe..e329b1e481 100644 --- a/desktop/src/features/pulse/ui/NoteCard.tsx +++ b/desktop/src/features/pulse/ui/NoteCard.tsx @@ -191,7 +191,7 @@ export function NoteCard({ {isAgent ? ( - + bot ) : null} diff --git a/desktop/src/features/pulse/ui/PulseTabBar.tsx b/desktop/src/features/pulse/ui/PulseTabBar.tsx index 937682c7ed..cc3793b936 100644 --- a/desktop/src/features/pulse/ui/PulseTabBar.tsx +++ b/desktop/src/features/pulse/ui/PulseTabBar.tsx @@ -103,7 +103,7 @@ export function PulseTabBar({ > Agents {relayAgents.length > 0 ? ( - + {relayAgents.length} ) : null} diff --git a/desktop/src/features/search/ui/TopbarSearch.tsx b/desktop/src/features/search/ui/TopbarSearch.tsx index 3ee05c49c5..60bd1abc29 100644 --- a/desktop/src/features/search/ui/TopbarSearch.tsx +++ b/desktop/src/features/search/ui/TopbarSearch.tsx @@ -201,7 +201,7 @@ export function TopbarSearch({ placeholder="Search everything" value={query} /> - + ⌘K @@ -212,7 +212,7 @@ export function TopbarSearch({ data-testid="search-results" > {debouncedQuery.length < MIN_SEARCH_QUERY_LENGTH ? ( -
+

Type at least two characters for live suggestions.

) : searchQuery.isLoading && results.length === 0 ? ( @@ -291,7 +291,7 @@ export function TopbarSearch({ : truncateResultText(result.hit.content)} - + {result.kind === "channel" ? "Channel" : `${describeSearchHit(result.hit)} · ${formatRelativeTime(result.hit.createdAt)}`} diff --git a/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx b/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx index 0181e19288..ff01e693c5 100644 --- a/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx +++ b/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx @@ -216,7 +216,10 @@ function TemplateRow({
{template.name} {template.isBuiltin ? ( - + built-in ) : null} @@ -756,7 +759,7 @@ function RuntimeRow({ ) : ( )} diff --git a/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx b/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx index a6d3044cdc..c5c27149b9 100644 --- a/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx +++ b/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx @@ -109,7 +109,7 @@ function RuntimeRow({

{runtime.label}

{runtime.command ? ( - + {runtime.command} ) : null} @@ -134,21 +134,21 @@ function RuntimeRow({ {runtime.underlyingCliPath && runtime.underlyingCliPath !== runtime.binaryPath ? (
-

+

CLI:{" "} {runtime.underlyingCliPath}

-

+

ACP adapter:{" "} {runtime.binaryPath}

) : ( <> -

+

{runtime.binaryPath}

-

+

ACP support built-in — no separate adapter needed.

@@ -158,7 +158,7 @@ function RuntimeRow({ <>

CLI detected at{" "} - + {runtime.underlyingCliPath ?? "unknown path"} {" "} but ACP adapter not found. @@ -176,7 +176,7 @@ function RuntimeRow({ <>

ACP adapter found at{" "} - + {runtime.binaryPath ?? "unknown path"} {" "} but the {runtime.label} CLI is not installed. diff --git a/desktop/src/features/settings/ui/NotificationSettingsCard.tsx b/desktop/src/features/settings/ui/NotificationSettingsCard.tsx index 55a0c9ab77..be85a6bb67 100644 --- a/desktop/src/features/settings/ui/NotificationSettingsCard.tsx +++ b/desktop/src/features/settings/ui/NotificationSettingsCard.tsx @@ -179,7 +179,7 @@ export function NotificationSettingsCard({ {SLOT_LABELS[slot]} {comingSoon ? ( - + Coming soon ) : null} diff --git a/desktop/src/features/settings/ui/ProfileSettingsCard.tsx b/desktop/src/features/settings/ui/ProfileSettingsCard.tsx index cfe3c9bb20..7c49aa0b1e 100644 --- a/desktop/src/features/settings/ui/ProfileSettingsCard.tsx +++ b/desktop/src/features/settings/ui/ProfileSettingsCard.tsx @@ -429,7 +429,7 @@ export function ProfileSettingsCard({ > 0 && "buzz-avatar-squish", )} data-testid="profile-avatar-preview-emoji" diff --git a/desktop/src/features/settings/ui/SoundPicker.tsx b/desktop/src/features/settings/ui/SoundPicker.tsx index 5de4f450c6..440fd39fcc 100644 --- a/desktop/src/features/settings/ui/SoundPicker.tsx +++ b/desktop/src/features/settings/ui/SoundPicker.tsx @@ -113,7 +113,7 @@ export function SoundPicker({ {name} {name === recommended ? ( - + rec. ) : null} diff --git a/desktop/src/features/sidebar/ui/AppSidebar.tsx b/desktop/src/features/sidebar/ui/AppSidebar.tsx index 00e8af524d..0106121a71 100644 --- a/desktop/src/features/sidebar/ui/AppSidebar.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebar.tsx @@ -444,7 +444,7 @@ export function AppSidebar({ {homeBadgeCount > 0 ? ( {Math.min(homeBadgeCount, 99)} @@ -492,7 +492,7 @@ export function AppSidebar({ {shouldShowAgentCount ? ( {totalAgentCount} diff --git a/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx b/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx index 5ebca7e47e..677ffe9b0a 100644 --- a/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx +++ b/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx @@ -3,7 +3,7 @@ import type * as React from "react"; import { Button } from "@/shared/ui/button"; const MORE_UNREAD_BUTTON_CLASS = - "h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[11px] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5"; + "h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[0.6875rem] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5"; export function MoreUnreadButton({ bottomClassName = "bottom-0", diff --git a/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx b/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx index 70b34d6a3f..557ffcffd3 100644 --- a/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx +++ b/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx @@ -180,7 +180,7 @@ export function NewDirectMessageDialog({ > diff --git a/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx b/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx index 97547a576e..654403f20d 100644 --- a/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx +++ b/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx @@ -78,7 +78,10 @@ export function SidebarProfileCard({ const workspaceLabel = activeWorkspace?.name ?? "No workspace"; const readonlyWorkspaceLabel = ( -

-
+
{channelName ? {channelName} : null} {triggerSummary ? {triggerSummary} : null} diff --git a/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx b/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx index faf22857f8..19ff131999 100644 --- a/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx +++ b/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx @@ -186,7 +186,7 @@ export function WorkflowDetailPanel({
-
+
{new Date( run.createdAt * 1000, @@ -216,10 +216,10 @@ export function WorkflowDetailPanel({ {isSelected ? (
-
+
Execution Trace {approvalsQuery.isFetching ? ( - + Refreshing approvals... ) : null} diff --git a/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx b/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx index a1e6b2775e..c83743a6a8 100644 --- a/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx +++ b/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx @@ -94,7 +94,7 @@ export function WorkflowRunTrace({
{Object.keys(step.output).length > 0 ? (
-

+

Output

@@ -104,7 +104,7 @@ export function WorkflowRunTrace({
             ) : null}
             {step.error ? (
               
-

+

Error

@@ -114,7 +114,7 @@ export function WorkflowRunTrace({
             ) : null}
             {pendingApproval ? (
               
-

+

Pending approval

diff --git a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx index 2e7e14d045..39c61f91c2 100644 --- a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx +++ b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx @@ -132,7 +132,7 @@ export function WorkspaceSwitcher({ diff --git a/desktop/src/shared/ui/UserAvatar.tsx b/desktop/src/shared/ui/UserAvatar.tsx index cab6a8fd66..0846367557 100644 --- a/desktop/src/shared/ui/UserAvatar.tsx +++ b/desktop/src/shared/ui/UserAvatar.tsx @@ -6,8 +6,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/shared/ui/avatar"; type UserAvatarSize = "xs" | "sm" | "md"; const sizeClasses: Record = { - xs: "h-5 w-5 text-[8px]", - sm: "h-6 w-6 text-[9px]", + xs: "h-5 w-5 text-[0.5rem]", + sm: "h-6 w-6 text-[0.5625rem]", md: "h-10 w-10 text-xs", }; diff --git a/desktop/src/shared/ui/VideoPlayer.tsx b/desktop/src/shared/ui/VideoPlayer.tsx index b967b00e95..db8a78b753 100644 --- a/desktop/src/shared/ui/VideoPlayer.tsx +++ b/desktop/src/shared/ui/VideoPlayer.tsx @@ -531,7 +531,7 @@ function VideoScrubber({ style={{ left: `${hoverRatio * 100}%` }} >
- + {formatTimecode(hoverRatio * duration)} / {formatTimecode(duration)}
@@ -918,7 +918,7 @@ export function VideoPlayer({ )} {formatTimecode(currentTime)} @@ -932,7 +932,7 @@ export function VideoPlayer({ testIdPrefix="video-inline" /> {formatTimecode(duration)} @@ -1716,12 +1716,12 @@ function VideoReviewDialog({ {formatTimecode(currentTime)} {replyTarget ? ( - + Replying to {replyTarget.comment.author} ) : (
) : (
-
+
{selectedAgent.name} {selectedAgent.status}
diff --git a/desktop/src/features/agents/ui/ManagedAgentRow.tsx b/desktop/src/features/agents/ui/ManagedAgentRow.tsx index ba06918e9e..e08f20e296 100644 --- a/desktop/src/features/agents/ui/ManagedAgentRow.tsx +++ b/desktop/src/features/agents/ui/ManagedAgentRow.tsx @@ -349,7 +349,7 @@ function StatusBlock({ }) { return (
-

+

Status

-

+

Runtime

diff --git a/desktop/src/features/agents/ui/ModelPicker.tsx b/desktop/src/features/agents/ui/ModelPicker.tsx index 193bc83d6a..4ac9bb39a3 100644 --- a/desktop/src/features/agents/ui/ModelPicker.tsx +++ b/desktop/src/features/agents/ui/ModelPicker.tsx @@ -152,7 +152,7 @@ export function ModelPicker({ {needsRestart ? ( - restart to apply + restart to apply ) : null} ); diff --git a/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx b/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx index 000b9075c5..0329b6c807 100644 --- a/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx +++ b/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx @@ -14,7 +14,7 @@ export function PersonaCatalogSelectionBadge({ return (

-

+

Raw ACP

JSON-RPC payloads

@@ -40,7 +40,7 @@ export function RawEventRail({ events }: { events: ObserverEvent[] }) { #{event.seq}{" "} {describeRawEvent(event)} -
+                
                   {JSON.stringify(event.payload, null, 2)}
                 
diff --git a/desktop/src/features/agents/ui/RelayDirectorySection.tsx b/desktop/src/features/agents/ui/RelayDirectorySection.tsx index 4e91a455f6..d1ac88c620 100644 --- a/desktop/src/features/agents/ui/RelayDirectorySection.tsx +++ b/desktop/src/features/agents/ui/RelayDirectorySection.tsx @@ -95,7 +95,7 @@ export function RelayDirectorySection({ className="w-full border-collapse text-left text-sm" data-testid="relay-directory-table" > - + Agent Status @@ -121,7 +121,7 @@ export function RelayDirectorySection({ diff --git a/desktop/src/features/agents/ui/RespondToField.tsx b/desktop/src/features/agents/ui/RespondToField.tsx index bc73ca4e6e..3f7b675aa4 100644 --- a/desktop/src/features/agents/ui/RespondToField.tsx +++ b/desktop/src/features/agents/ui/RespondToField.tsx @@ -211,7 +211,7 @@ function AllowlistPicker({ >
Allowed pubkeys - + {allowlist.length} selected
@@ -241,7 +241,7 @@ function AllowlistPicker({
{allowlist.map((pubkey) => (
diff --git a/desktop/src/features/agents/ui/TeamDialog.tsx b/desktop/src/features/agents/ui/TeamDialog.tsx index 8d765100d1..504f6acf30 100644 --- a/desktop/src/features/agents/ui/TeamDialog.tsx +++ b/desktop/src/features/agents/ui/TeamDialog.tsx @@ -422,7 +422,7 @@ export function TeamDialog({ /> {persona.displayName} diff --git a/desktop/src/features/agents/ui/TeamsSection.tsx b/desktop/src/features/agents/ui/TeamsSection.tsx index 500d5a09e0..9be5a9bb8b 100644 --- a/desktop/src/features/agents/ui/TeamsSection.tsx +++ b/desktop/src/features/agents/ui/TeamsSection.tsx @@ -172,7 +172,7 @@ export function TeamsSection({ ) : null} {team.version ? ( - + v{team.version} ) : null} @@ -199,13 +199,13 @@ export function TeamsSection({ {visible.map((persona) => ( ))} {overflow > 0 ? ( - + +{overflow} ) : null} diff --git a/desktop/src/features/channels/ui/AddChannelBotPersonasSection.tsx b/desktop/src/features/channels/ui/AddChannelBotPersonasSection.tsx index ed7309f1d2..16b0806a4c 100644 --- a/desktop/src/features/channels/ui/AddChannelBotPersonasSection.tsx +++ b/desktop/src/features/channels/ui/AddChannelBotPersonasSection.tsx @@ -49,7 +49,7 @@ function SelectionChipButton({

{persona.displayName}

{isInChannel ? ( -

+

✓ Already in this channel

) : null} -

+

{promptPreview(persona.systemPrompt)}

diff --git a/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx b/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx index 1fe45f9ae7..2879902c73 100644 --- a/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx +++ b/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx @@ -124,7 +124,7 @@ export function AddChannelBotTeamsSection({ {inChannelCount > 0 ? (

{team.name}

{team.description ? ( -

+

{team.description}

) : null} @@ -158,10 +158,10 @@ export function AddChannelBotTeamsSection({ > - + {persona.displayName} {personaInChannel ? ( diff --git a/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx b/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx index 6a8c508cae..174de97143 100644 --- a/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx +++ b/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx @@ -79,10 +79,7 @@ export function AgentSessionThreadPanel({ const agentHeaderActions = (
{isLive && isWorking ? ( - + Live @@ -92,7 +89,7 @@ export function AgentSessionThreadPanel({
{inviteTargets.length > 0 ? ( - + {inviteTargets.length} selected ) : null} @@ -210,7 +210,7 @@ export function ChannelMemberInviteCard({
{selectedInvitees.map((invitee) => (
diff --git a/desktop/src/features/channels/ui/ChannelScreenHeader.tsx b/desktop/src/features/channels/ui/ChannelScreenHeader.tsx index 59b39fdc28..e2e752924a 100644 --- a/desktop/src/features/channels/ui/ChannelScreenHeader.tsx +++ b/desktop/src/features/channels/ui/ChannelScreenHeader.tsx @@ -92,7 +92,7 @@ export function ChannelScreenHeader({ activeChannel?.channelType === "dm" ? (

People

- + {people.length}
@@ -284,7 +284,7 @@ export function MembersSidebar({

Bots

- + {bots.length} {hasControllableManagedBots ? ( @@ -329,7 +329,7 @@ export function MembersSidebar({ Archived

{archived.length} diff --git a/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx b/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx index 4cb083c30b..cc09eea86f 100644 --- a/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx +++ b/desktop/src/features/channels/ui/MembersSidebarMemberCard.tsx @@ -119,7 +119,7 @@ export function MembersSidebarMemberCard({
@@ -159,7 +159,7 @@ export function MembersSidebarMemberCard({ ) : null}
-

+

{truncatePubkey(member.pubkey)}

diff --git a/desktop/src/features/channels/ui/QuickBotBar.tsx b/desktop/src/features/channels/ui/QuickBotBar.tsx index 572811a26c..06a53e3622 100644 --- a/desktop/src/features/channels/ui/QuickBotBar.tsx +++ b/desktop/src/features/channels/ui/QuickBotBar.tsx @@ -75,7 +75,7 @@ export function QuickBotBar({ personas, pending, onAdd }: QuickBotBarProps) { src={rewriteRelayUrl(persona.avatarUrl)} /> ) : ( - + {initials} )} diff --git a/desktop/src/features/home/ui/FeedSection.tsx b/desktop/src/features/home/ui/FeedSection.tsx index a42d3d674a..4b71f6edde 100644 --- a/desktop/src/features/home/ui/FeedSection.tsx +++ b/desktop/src/features/home/ui/FeedSection.tsx @@ -187,11 +187,11 @@ export function FeedSection({
{feedHeadline(item)} - + {item.channelName ? ( - + #{item.channelName} ) : null} - + {formatRelativeTime(item.createdAt)}
{isThreadContextLoading ? ( -
+
Loading context...
) : null} diff --git a/desktop/src/features/home/ui/InboxListPane.tsx b/desktop/src/features/home/ui/InboxListPane.tsx index b45575c604..2ff283d0b4 100644 --- a/desktop/src/features/home/ui/InboxListPane.tsx +++ b/desktop/src/features/home/ui/InboxListPane.tsx @@ -67,7 +67,7 @@ export function InboxListPane({
diff --git a/desktop/src/features/messages/ui/TypingIndicatorRow.tsx b/desktop/src/features/messages/ui/TypingIndicatorRow.tsx index b0bfb01981..67e2a4f551 100644 --- a/desktop/src/features/messages/ui/TypingIndicatorRow.tsx +++ b/desktop/src/features/messages/ui/TypingIndicatorRow.tsx @@ -113,8 +113,8 @@ export function TypingIndicatorRow({ label={label} className={cn( isActivityVariant - ? "h-[18px] w-[18px] text-[0.4375rem]" - : "h-5 w-5 text-[0.5rem]", + ? "h-[18px] w-[18px] text-3xs" + : "h-5 w-5 text-3xs", )} iconClassName={ isActivityVariant ? "h-2.5 w-2.5" : "h-3 w-3" diff --git a/desktop/src/features/onboarding/ui/MembershipDenied.tsx b/desktop/src/features/onboarding/ui/MembershipDenied.tsx index 1a20572f3d..4f253e8c5d 100644 --- a/desktop/src/features/onboarding/ui/MembershipDenied.tsx +++ b/desktop/src/features/onboarding/ui/MembershipDenied.tsx @@ -177,7 +177,7 @@ export function MembershipDenied({

This will use this Nostr identity:

-

+

{shortenNpub(previewNpub)}

diff --git a/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx b/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx index cbff60e55b..d2ae1e7c82 100644 --- a/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx +++ b/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx @@ -227,7 +227,7 @@ export function NostrKeyImportForm({

This will use this Nostr identity:

-

+

{shortenNpub(previewNpub)}

diff --git a/desktop/src/features/profile/ui/UserProfilePopover.tsx b/desktop/src/features/profile/ui/UserProfilePopover.tsx index 9350b98b94..b5146a5e97 100644 --- a/desktop/src/features/profile/ui/UserProfilePopover.tsx +++ b/desktop/src/features/profile/ui/UserProfilePopover.tsx @@ -216,7 +216,7 @@ export function UserProfilePopover({

) : null} {profile?.displayName ? ( -

+

{truncatePubkey(pubkey)}

) : null} diff --git a/desktop/src/features/pulse/ui/AgentActivityCard.tsx b/desktop/src/features/pulse/ui/AgentActivityCard.tsx index fc11f6554e..07f3836450 100644 --- a/desktop/src/features/pulse/ui/AgentActivityCard.tsx +++ b/desktop/src/features/pulse/ui/AgentActivityCard.tsx @@ -75,7 +75,7 @@ export function AgentActivityCard({ {displayName}
{agentStatus ? : null} - + {formatRelativeTime(group.latestAt)} @@ -113,7 +113,7 @@ export function AgentActivityCard({
-

+

{formatRelativeTime(note.createdAt)}

diff --git a/desktop/src/features/pulse/ui/NoteCard.tsx b/desktop/src/features/pulse/ui/NoteCard.tsx index e329b1e481..e3ba40ae7a 100644 --- a/desktop/src/features/pulse/ui/NoteCard.tsx +++ b/desktop/src/features/pulse/ui/NoteCard.tsx @@ -191,7 +191,7 @@ export function NoteCard({ {isAgent ? ( - + bot ) : null} diff --git a/desktop/src/features/pulse/ui/PulseTabBar.tsx b/desktop/src/features/pulse/ui/PulseTabBar.tsx index cc3793b936..3ba3b0777a 100644 --- a/desktop/src/features/pulse/ui/PulseTabBar.tsx +++ b/desktop/src/features/pulse/ui/PulseTabBar.tsx @@ -13,7 +13,7 @@ type PulseTabBarProps = { }; const tabButtonClassName = - "h-7 rounded-full border border-transparent px-1.5 text-[10.5px] font-medium text-muted-foreground data-[active=true]:border-border/70 data-[active=true]:bg-background/80 data-[active=true]:text-foreground data-[active=true]:shadow-xs data-[active=true]:backdrop-blur-sm"; + "h-7 rounded-full border border-transparent px-1.5 text-2xs font-medium text-muted-foreground data-[active=true]:border-border/70 data-[active=true]:bg-background/80 data-[active=true]:text-foreground data-[active=true]:shadow-xs data-[active=true]:backdrop-blur-sm"; export function PulseTabBar({ activeTab, @@ -103,7 +103,7 @@ export function PulseTabBar({ > Agents {relayAgents.length > 0 ? ( - + {relayAgents.length} ) : null} diff --git a/desktop/src/features/search/ui/TopbarSearch.tsx b/desktop/src/features/search/ui/TopbarSearch.tsx index 60bd1abc29..bea3f62dcb 100644 --- a/desktop/src/features/search/ui/TopbarSearch.tsx +++ b/desktop/src/features/search/ui/TopbarSearch.tsx @@ -201,7 +201,7 @@ export function TopbarSearch({ placeholder="Search everything" value={query} /> - + ⌘K @@ -212,7 +212,7 @@ export function TopbarSearch({ data-testid="search-results" > {debouncedQuery.length < MIN_SEARCH_QUERY_LENGTH ? ( -
+

Type at least two characters for live suggestions.

) : searchQuery.isLoading && results.length === 0 ? ( @@ -291,7 +291,7 @@ export function TopbarSearch({ : truncateResultText(result.hit.content)} - + {result.kind === "channel" ? "Channel" : `${describeSearchHit(result.hit)} · ${formatRelativeTime(result.hit.createdAt)}`} diff --git a/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx b/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx index ff01e693c5..bfae506c5e 100644 --- a/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx +++ b/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx @@ -216,10 +216,7 @@ function TemplateRow({
{template.name} {template.isBuiltin ? ( - + built-in ) : null} @@ -759,7 +756,7 @@ function RuntimeRow({ ) : ( )} diff --git a/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx b/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx index c5c27149b9..a457ecb062 100644 --- a/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx +++ b/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx @@ -109,7 +109,7 @@ function RuntimeRow({

{runtime.label}

{runtime.command ? ( - + {runtime.command} ) : null} @@ -134,21 +134,21 @@ function RuntimeRow({ {runtime.underlyingCliPath && runtime.underlyingCliPath !== runtime.binaryPath ? (
-

+

CLI:{" "} {runtime.underlyingCliPath}

-

+

ACP adapter:{" "} {runtime.binaryPath}

) : ( <> -

+

{runtime.binaryPath}

-

+

ACP support built-in — no separate adapter needed.

@@ -158,7 +158,7 @@ function RuntimeRow({ <>

CLI detected at{" "} - + {runtime.underlyingCliPath ?? "unknown path"} {" "} but ACP adapter not found. @@ -176,7 +176,7 @@ function RuntimeRow({ <>

ACP adapter found at{" "} - + {runtime.binaryPath ?? "unknown path"} {" "} but the {runtime.label} CLI is not installed. diff --git a/desktop/src/features/settings/ui/NotificationSettingsCard.tsx b/desktop/src/features/settings/ui/NotificationSettingsCard.tsx index be85a6bb67..af7246ca9b 100644 --- a/desktop/src/features/settings/ui/NotificationSettingsCard.tsx +++ b/desktop/src/features/settings/ui/NotificationSettingsCard.tsx @@ -179,7 +179,7 @@ export function NotificationSettingsCard({ {SLOT_LABELS[slot]} {comingSoon ? ( - + Coming soon ) : null} diff --git a/desktop/src/features/settings/ui/SoundPicker.tsx b/desktop/src/features/settings/ui/SoundPicker.tsx index 440fd39fcc..95d7a0835e 100644 --- a/desktop/src/features/settings/ui/SoundPicker.tsx +++ b/desktop/src/features/settings/ui/SoundPicker.tsx @@ -113,7 +113,7 @@ export function SoundPicker({ {name} {name === recommended ? ( - + rec. ) : null} diff --git a/desktop/src/features/sidebar/ui/AppSidebar.tsx b/desktop/src/features/sidebar/ui/AppSidebar.tsx index 0106121a71..0b0e21bfc1 100644 --- a/desktop/src/features/sidebar/ui/AppSidebar.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebar.tsx @@ -444,7 +444,7 @@ export function AppSidebar({ {homeBadgeCount > 0 ? ( {Math.min(homeBadgeCount, 99)} @@ -492,7 +492,7 @@ export function AppSidebar({ {shouldShowAgentCount ? ( {totalAgentCount} diff --git a/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx b/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx index 677ffe9b0a..e7769d2d6f 100644 --- a/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx +++ b/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx @@ -3,7 +3,7 @@ import type * as React from "react"; import { Button } from "@/shared/ui/button"; const MORE_UNREAD_BUTTON_CLASS = - "h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[0.6875rem] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5"; + "h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-2xs font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5"; export function MoreUnreadButton({ bottomClassName = "bottom-0", diff --git a/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx b/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx index 557ffcffd3..6aaf42bd91 100644 --- a/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx +++ b/desktop/src/features/sidebar/ui/NewDirectMessageDialog.tsx @@ -180,7 +180,7 @@ export function NewDirectMessageDialog({ > diff --git a/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx b/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx index 654403f20d..910e9904ee 100644 --- a/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx +++ b/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx @@ -78,10 +78,7 @@ export function SidebarProfileCard({ const workspaceLabel = activeWorkspace?.name ?? "No workspace"; const readonlyWorkspaceLabel = ( -

-
+
{channelName ? {channelName} : null} {triggerSummary ? {triggerSummary} : null} diff --git a/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx b/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx index 19ff131999..9660f5f8d4 100644 --- a/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx +++ b/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx @@ -186,7 +186,7 @@ export function WorkflowDetailPanel({
-
+
{new Date( run.createdAt * 1000, @@ -216,10 +216,10 @@ export function WorkflowDetailPanel({ {isSelected ? (
-
+
Execution Trace {approvalsQuery.isFetching ? ( - + Refreshing approvals... ) : null} diff --git a/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx b/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx index c83743a6a8..76fc9ecf88 100644 --- a/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx +++ b/desktop/src/features/workflows/ui/WorkflowRunTrace.tsx @@ -94,7 +94,7 @@ export function WorkflowRunTrace({
{Object.keys(step.output).length > 0 ? (
-

+

Output

@@ -104,7 +104,7 @@ export function WorkflowRunTrace({
             ) : null}
             {step.error ? (
               
-

+

Error

@@ -114,7 +114,7 @@ export function WorkflowRunTrace({
             ) : null}
             {pendingApproval ? (
               
-

+

Pending approval

diff --git a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx index 39c61f91c2..2703087d84 100644 --- a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx +++ b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx @@ -132,7 +132,7 @@ export function WorkspaceSwitcher({ diff --git a/desktop/src/shared/ui/UserAvatar.tsx b/desktop/src/shared/ui/UserAvatar.tsx index 0846367557..2f52436ff8 100644 --- a/desktop/src/shared/ui/UserAvatar.tsx +++ b/desktop/src/shared/ui/UserAvatar.tsx @@ -6,8 +6,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/shared/ui/avatar"; type UserAvatarSize = "xs" | "sm" | "md"; const sizeClasses: Record = { - xs: "h-5 w-5 text-[0.5rem]", - sm: "h-6 w-6 text-[0.5625rem]", + xs: "h-5 w-5 text-3xs", + sm: "h-6 w-6 text-2xs", md: "h-10 w-10 text-xs", }; diff --git a/desktop/src/shared/ui/VideoPlayer.tsx b/desktop/src/shared/ui/VideoPlayer.tsx index db8a78b753..d011be3adc 100644 --- a/desktop/src/shared/ui/VideoPlayer.tsx +++ b/desktop/src/shared/ui/VideoPlayer.tsx @@ -531,7 +531,7 @@ function VideoScrubber({ style={{ left: `${hoverRatio * 100}%` }} >
- + {formatTimecode(hoverRatio * duration)} / {formatTimecode(duration)}
@@ -918,7 +918,7 @@ export function VideoPlayer({ )} {formatTimecode(currentTime)} @@ -932,7 +932,7 @@ export function VideoPlayer({ testIdPrefix="video-inline" /> {formatTimecode(duration)} @@ -1716,12 +1716,12 @@ function VideoReviewDialog({ {formatTimecode(currentTime)} {replyTarget ? ( - + Replying to {replyTarget.comment.author} ) : (
diff --git a/desktop/src/features/home/ui/InboxMessageRow.tsx b/desktop/src/features/home/ui/InboxMessageRow.tsx index 8852488b94..81062f109f 100644 --- a/desktop/src/features/home/ui/InboxMessageRow.tsx +++ b/desktop/src/features/home/ui/InboxMessageRow.tsx @@ -131,7 +131,6 @@ export function InboxMessageRow({ className="max-w-full text-left text-sm text-foreground" content={message.content} mentionNames={message.mentionNames} - tight />
- +
diff --git a/desktop/src/features/messages/ui/MessageRow.tsx b/desktop/src/features/messages/ui/MessageRow.tsx index 424410cdc3..18de77a941 100644 --- a/desktop/src/features/messages/ui/MessageRow.tsx +++ b/desktop/src/features/messages/ui/MessageRow.tsx @@ -199,7 +199,6 @@ export const MessageRow = React.memo( mentionNames={mentionNames} mentionPubkeysByName={mentionPubkeysByName} searchQuery={searchQuery} - tight videoReviewContext={videoReviewContext} /> ); diff --git a/desktop/src/features/pulse/ui/NoteCard.tsx b/desktop/src/features/pulse/ui/NoteCard.tsx index e3ba40ae7a..4b906cb44b 100644 --- a/desktop/src/features/pulse/ui/NoteCard.tsx +++ b/desktop/src/features/pulse/ui/NoteCard.tsx @@ -213,7 +213,7 @@ export function NoteCard({ ) : null}
- +
diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index ab298d5817..2e6f84a198 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -201,7 +201,6 @@ function messageLinkUrlTransform(value: string, key: string): string { type MarkdownProps = { channelNames?: string[]; className?: string; - compact?: boolean; content: string; customEmoji?: CustomEmoji[]; imetaByUrl?: ImetaLookup; @@ -210,12 +209,9 @@ type MarkdownProps = { mentionNames?: string[]; mentionPubkeysByName?: Record; searchQuery?: string; - tight?: boolean; videoReviewContext?: VideoReviewContext; }; -type MarkdownVariant = "default" | "compact" | "tight"; - /** * Inline image embed with click-to-zoom lightbox and right-click download. * @@ -730,7 +726,6 @@ function SyntaxHighlightedCode({ ); } function createMarkdownComponents( - variant: MarkdownVariant, channels: Channel[], onOpenChannel: (channelId: string) => void, onOpenMessageLink: (link: ParsedMessageLink) => void, @@ -739,18 +734,10 @@ function createMarkdownComponents( agentMentionPubkeysByName?: Record, interactive = true, ): Components { - const paragraphClassName = - variant === "tight" - ? "leading-5" - : variant === "compact" - ? "leading-6" - : "leading-7"; - const listItemClassName = - variant === "tight" ? "my-0.5 [&_p]:inline" : "my-1 [&_p]:inline"; + const paragraphClassName = "leading-6"; + const listItemClassName = "my-1 [&_p]:inline"; const listClassName = - variant === "tight" - ? "space-y-0.5 pl-6 marker:text-muted-foreground" - : "space-y-1 pl-6 marker:text-muted-foreground"; + "space-y-1 pl-6 marker:text-muted-foreground"; return { a: ({ children, href, ...props }) => { @@ -1093,7 +1080,6 @@ function createMarkdownComponents( function MarkdownInner({ channelNames, className, - compact = false, content, customEmoji, imetaByUrl, @@ -1102,14 +1088,8 @@ function MarkdownInner({ mentionNames, mentionPubkeysByName, searchQuery, - tight = false, videoReviewContext, }: MarkdownProps) { - const variant: MarkdownVariant = tight - ? "tight" - : compact - ? "compact" - : "default"; const { channels: rawChannels } = useChannelNavigation(); const channels = useStableArray(rawChannels); const { goChannel } = useAppNavigation(); @@ -1117,7 +1097,6 @@ function MarkdownInner({ const components = React.useMemo( () => createMarkdownComponents( - variant, channels, (channelId) => { void goChannel(channelId); @@ -1142,7 +1121,6 @@ function MarkdownInner({ ), [ goChannel, - variant, channels, imetaByUrl, mentionPubkeysByName, @@ -1198,52 +1176,18 @@ function MarkdownInner({ return (
*:first-child]:mt-0 [&>*:last-child]:mb-0", - // Base owl: p+p, list+p, etc. - "[&>*+*]:mt-2", - // Headings: flat push/pull — size does the hierarchy work - "[&>*+h1]:mt-2.5 [&>*+h2]:mt-2.5 [&>*+h3]:mt-2.5", - "[&>h1+*]:mt-0.5 [&>h2+*]:mt-0.5 [&>h3+*]:mt-0.5", - // Blockquotes: breathe above and below - "[&>*+blockquote]:mt-3 [&>blockquote+*]:mt-3", - // Code blocks: breathe above and below - "[&>*+[data-code-block]]:mt-3 [&>[data-code-block]+*]:mt-3", - // Tables: breathe above and below - "[&>*+[data-table-block]]:mt-3 [&>[data-table-block]+*]:mt-3", - // hr: clear section divider - "[&>*+hr]:mt-3.5 [&>hr+*]:mt-3.5", - // Lists after paragraphs: tighter to feel related - "[&>p+ul]:mt-1 [&>p+ol]:mt-1 [&>div+ul]:mt-1 [&>div+ol]:mt-1", - ].join(" ") - : compact - ? [ - "max-w-none break-words text-base text-foreground/90", - "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", - "[&>*+*]:mt-2", - "[&>*+h1]:mt-3 [&>*+h2]:mt-3 [&>*+h3]:mt-3", - "[&>h1+*]:mt-0.5 [&>h2+*]:mt-0.5 [&>h3+*]:mt-0.5", - "[&>*+blockquote]:mt-3 [&>blockquote+*]:mt-3", - "[&>*+[data-code-block]]:mt-3 [&>[data-code-block]+*]:mt-3", - "[&>*+[data-table-block]]:mt-3 [&>[data-table-block]+*]:mt-3", - "[&>*+hr]:mt-3.5 [&>hr+*]:mt-3.5", - "[&>p+ul]:mt-1 [&>p+ol]:mt-1 [&>div+ul]:mt-1 [&>div+ol]:mt-1", - ].join(" ") - : [ - "max-w-none break-words text-sm leading-7 text-foreground/90", - "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", - "[&>*+*]:mt-3", - "[&>*+h1]:mt-3.5 [&>*+h2]:mt-3.5 [&>*+h3]:mt-3.5", - "[&>h1+*]:mt-0.5 [&>h2+*]:mt-0.5 [&>h3+*]:mt-0.5", - "[&>*+blockquote]:mt-3.5 [&>blockquote+*]:mt-3.5", - "[&>*+[data-code-block]]:mt-3.5 [&>[data-code-block]+*]:mt-3.5", - "[&>*+[data-table-block]]:mt-3.5 [&>[data-table-block]+*]:mt-3.5", - "[&>*+hr]:mt-4 [&>hr+*]:mt-4", - "[&>p+ul]:mt-1.5 [&>p+ol]:mt-1.5 [&>div+ul]:mt-1.5 [&>div+ol]:mt-1.5", - ].join(" "), + [ + "max-w-none break-words text-sm leading-6 text-foreground/90", + "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", + "[&>*+*]:mt-3", + "[&>*+h1]:mt-3.5 [&>*+h2]:mt-3.5 [&>*+h3]:mt-3.5", + "[&>h1+*]:mt-0.5 [&>h2+*]:mt-0.5 [&>h3+*]:mt-0.5", + "[&>*+blockquote]:mt-3.5 [&>blockquote+*]:mt-3.5", + "[&>*+[data-code-block]]:mt-3.5 [&>[data-code-block]+*]:mt-3.5", + "[&>*+[data-table-block]]:mt-3.5 [&>[data-table-block]+*]:mt-3.5", + "[&>*+hr]:mt-4 [&>hr+*]:mt-4", + "[&>p+ul]:mt-1.5 [&>p+ol]:mt-1.5 [&>div+ul]:mt-1.5 [&>div+ol]:mt-1.5", + ].join(" "), className, )} > @@ -1259,10 +1203,8 @@ export const Markdown = React.memo( (prev, next) => prev.content === next.content && prev.className === next.className && - prev.compact === next.compact && prev.customEmoji === next.customEmoji && prev.interactive === next.interactive && - prev.tight === next.tight && prev.agentMentionPubkeysByName === next.agentMentionPubkeysByName && prev.mentionPubkeysByName === next.mentionPubkeysByName && shallowArrayEqual(prev.mentionNames, next.mentionNames) && From 8d11f7ff205d34e3f594be3673f116ef2d8f179f Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 15 Jun 2026 13:46:39 -0700 Subject: [PATCH 09/20] fix(desktop): unify mention chip and code styling - Move mention chip sizing and hover treatment into shared CSS classes consumed by timeline markdown and composer decorations - Apply shared chip styling to channel links and message links so inline semantic chips use one visual treatment - Keep bot mentions optically balanced with agent-specific right padding and a slightly larger robot mask icon - Align composer text with the chat base type size and reduce mention/code radii by one token for a tighter inline appearance --- .../messages/lib/mentionHighlightExtension.ts | 6 ++-- desktop/src/shared/styles/globals.css | 32 +++++++++++++------ desktop/src/shared/ui/markdown.tsx | 18 ++++++++--- desktop/src/shared/ui/mentionChip.ts | 6 ++-- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/desktop/src/features/messages/lib/mentionHighlightExtension.ts b/desktop/src/features/messages/lib/mentionHighlightExtension.ts index 53f7636744..63ebfba407 100644 --- a/desktop/src/features/messages/lib/mentionHighlightExtension.ts +++ b/desktop/src/features/messages/lib/mentionHighlightExtension.ts @@ -264,14 +264,14 @@ function buildDecorations( node.text, pos, mentionPatterns, - "mention-highlight", + "mention-highlight mention-chip", ); addMatchesForPatterns( decorations, node.text, pos, agentMentionPatterns, - "mention-highlight agent-mention-highlight", + "mention-highlight mention-chip agent-mention-highlight", { hideMentionPrefix: true }, ); addMatchesForPatterns( @@ -279,7 +279,7 @@ function buildDecorations( node.text, pos, channelPatterns, - "mention-highlight", + "mention-highlight mention-chip", ); }); diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css index 3fb1ef7eb3..34511ecb31 100644 --- a/desktop/src/shared/styles/globals.css +++ b/desktop/src/shared/styles/globals.css @@ -503,7 +503,7 @@ .rich-text-composer .tiptap { outline: none; min-height: 1.5rem; /* single line height */ - font-size: 0.875rem; + font-size: 1rem; line-height: 1.5rem; } @@ -546,7 +546,7 @@ } .rich-text-composer .tiptap code { - border-radius: 0.375rem; + border-radius: calc(var(--radius) - 4px); background: hsl(var(--muted)); padding: 0.125rem 0.375rem; font-family: ui-monospace, monospace; @@ -554,7 +554,7 @@ } .rich-text-composer .tiptap pre { - border-radius: 0.75rem; + border-radius: var(--radius); border: 1px solid hsl(var(--border) / 0.7); background: hsl(var(--muted) / 0.6); padding: 0.375rem 0.75rem; @@ -610,17 +610,30 @@ margin: 0.5rem 0; } +.mention-chip, .rich-text-composer .tiptap .mention-highlight { display: inline-block; - border-radius: 4px; + border-radius: calc(var(--radius) - 4px); background: hsl(var(--primary) / 0.15); - padding: 0.125rem 0.25rem; - font-size: inherit; - line-height: 1.1; + padding: 0 0.25rem; + font-size: 1rem; + line-height: 1.25rem; color: hsl(var(--primary)); font-weight: 500; } +.mention-chip-hover { + transition-property: color, background-color, border-color, outline-color, + text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.mention-chip-hover:hover { + background: hsl(var(--primary) / 0.25); + color: hsl(var(--primary) / 0.9); +} + .rich-text-composer .tiptap .agent-mention-at-hidden { color: transparent; font-size: 0; @@ -633,6 +646,7 @@ align-items: center; gap: 2px; background: hsl(var(--primary) / 0.18); + padding-right: 0.375rem; color: hsl(var(--primary) / 0.92); } @@ -644,8 +658,8 @@ [data-mention].agent-mention-highlight::before { content: ""; display: inline-block; - width: 0.85em; - height: 0.85em; + width: 0.9em; + height: 0.9em; flex: 0 0 auto; background: currentColor; transform: translateY(-0.25px); diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index 2e6f84a198..ba30a727a0 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -497,7 +497,7 @@ function MarkdownCodeBlock({ return (
-
+      
         {language && (
           
{language} @@ -837,7 +837,7 @@ function createMarkdownComponents( @@ -1018,7 +1018,11 @@ function createMarkdownComponents( type="button" data-channel-link="" aria-label={`Open channel ${channelName}`} - className="rounded-md bg-primary/15 px-1 py-0.5 text-sm font-medium text-primary cursor-pointer hover:bg-primary/25 transition-colors" + className={cn( + "cursor-pointer", + MENTION_CHIP_BASE_CLASSES, + MENTION_CHIP_HOVER_CLASSES, + )} onClick={() => { onOpenChannel(channel.id); }} @@ -1031,7 +1035,7 @@ function createMarkdownComponents( return ( {children} @@ -1065,7 +1069,11 @@ function createMarkdownComponents( data-message-link="" aria-label={`Open message in ${channelLabel}`} title={href} - className="rounded-md bg-primary/15 px-1 py-0.5 text-sm font-medium text-primary cursor-pointer hover:bg-primary/25 transition-colors" + className={cn( + "cursor-pointer", + MENTION_CHIP_BASE_CLASSES, + MENTION_CHIP_HOVER_CLASSES, + )} onClick={() => { onOpenMessageLink(parsed.value); }} diff --git a/desktop/src/shared/ui/mentionChip.ts b/desktop/src/shared/ui/mentionChip.ts index 583ee3ff3d..e63abb3ed6 100644 --- a/desktop/src/shared/ui/mentionChip.ts +++ b/desktop/src/shared/ui/mentionChip.ts @@ -1,5 +1,3 @@ -export const MENTION_CHIP_BASE_CLASSES = - "inline-block rounded-[4px] bg-primary/15 px-1 pt-[3px] pb-[2px] text-base font-medium leading-none text-primary"; +export const MENTION_CHIP_BASE_CLASSES = "mention-chip"; -export const MENTION_CHIP_HOVER_CLASSES = - "transition-colors hover:bg-primary/25 hover:text-primary/90"; +export const MENTION_CHIP_HOVER_CLASSES = "mention-chip-hover"; From 7366ebdc4de9da11f895cc3742b0494dcf1d9457 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 15 Jun 2026 14:09:06 -0700 Subject: [PATCH 10/20] fix(desktop): align message header typography - Add shared MessageHeaderRow and MessageAuthorText primitives for timeline header spacing and author/title typography - Use the shared header primitives in normal and system message rows so usernames, system titles, and timestamps align consistently - Reduce main timeline username size to match surrounding message surfaces and remove the extra body offset now that header line boxes align - Render system-event participants as profile names instead of mention chips and drop unused agent/persona props from system row plumbing --- .../features/messages/ui/MessageHeader.tsx | 50 ++++++++++++++++++ .../src/features/messages/ui/MessageRow.tsx | 25 ++++----- .../features/messages/ui/SystemMessageRow.tsx | 51 ++++--------------- .../messages/ui/TimelineMessageList.tsx | 2 - desktop/src/shared/ui/markdown.tsx | 8 +-- 5 files changed, 76 insertions(+), 60 deletions(-) create mode 100644 desktop/src/features/messages/ui/MessageHeader.tsx diff --git a/desktop/src/features/messages/ui/MessageHeader.tsx b/desktop/src/features/messages/ui/MessageHeader.tsx new file mode 100644 index 0000000000..9439f39be6 --- /dev/null +++ b/desktop/src/features/messages/ui/MessageHeader.tsx @@ -0,0 +1,50 @@ +import type * as React from "react"; + +import { cn } from "@/shared/lib/cn"; + +type MessageHeaderRowProps = { + children: React.ReactNode; + className?: string; +}; + +export function MessageHeaderRow({ + children, + className, +}: MessageHeaderRowProps) { + return ( +
+ {children} +
+ ); +} + +type MessageAuthorTextProps = { + as?: "div" | "h3" | "span"; + children: React.ReactNode; + className?: string; + hoverUnderline?: boolean; +}; + +export function MessageAuthorText({ + as: Component = "span", + children, + className, + hoverUnderline = false, +}: MessageAuthorTextProps) { + return ( + + {children} + + ); +} diff --git a/desktop/src/features/messages/ui/MessageRow.tsx b/desktop/src/features/messages/ui/MessageRow.tsx index 18de77a941..c20e40c049 100644 --- a/desktop/src/features/messages/ui/MessageRow.tsx +++ b/desktop/src/features/messages/ui/MessageRow.tsx @@ -20,6 +20,7 @@ import { import { Markdown } from "@/shared/ui/markdown"; import type { VideoReviewContext } from "@/shared/ui/VideoPlayer"; import { MessageActionBar } from "./MessageActionBar"; +import { MessageAuthorText, MessageHeaderRow } from "./MessageHeader"; import { MessageTimestamp } from "./MessageTimestamp"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; @@ -244,13 +245,13 @@ export const MessageRow = React.memo( ); const authorNode = message.pubkey ? ( - + {message.author} - + ) : ( -

+ {message.author} -

+ ); const actionBarNode = ( @@ -400,7 +401,7 @@ export const MessageRow = React.memo(
{avatarNode}
)}
-
+ {message.pubkey ? (
-
{messageBodyNode}
+ +
{messageBodyNode}
) : ( @@ -450,7 +451,7 @@ export const MessageRow = React.memo(
{avatarNode}
)}
-
+ {message.pubkey ? (
-
{messageBodyNode}
+ +
{messageBodyNode}
)} diff --git a/desktop/src/features/messages/ui/SystemMessageRow.tsx b/desktop/src/features/messages/ui/SystemMessageRow.tsx index 69ac851990..9a79974e73 100644 --- a/desktop/src/features/messages/ui/SystemMessageRow.tsx +++ b/desktop/src/features/messages/ui/SystemMessageRow.tsx @@ -9,16 +9,12 @@ import type { UserProfileLookup } from "@/features/profile/lib/identity"; import { resolveUserLabel } from "@/features/profile/lib/identity"; import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover"; import { cn } from "@/shared/lib/cn"; -import { normalizePubkey } from "@/shared/lib/pubkey"; import { Button } from "@/shared/ui/button"; import { isPositiveEmojiParticle } from "@/shared/ui/EmojiBurstProvider"; -import { - MENTION_CHIP_BASE_CLASSES, - MENTION_CHIP_HOVER_CLASSES, -} from "@/shared/ui/mentionChip"; import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; import { UserAvatar } from "@/shared/ui/UserAvatar"; +import { MessageAuthorText, MessageHeaderRow } from "./MessageHeader"; import { MessageTimestamp } from "./MessageTimestamp"; type SystemMessagePayload = { @@ -63,31 +59,18 @@ function resolveDisplayLabel( function ProfileName({ children, - highlight = false, - isAgent = false, pubkey, }: { children: React.ReactNode; - highlight?: boolean; - isAgent?: boolean; pubkey: string | undefined; }) { - const isAgentMention = highlight && isAgent; const node = ( - {highlight && !isAgentMention ? "@" : null} {children} ); @@ -192,14 +175,7 @@ function describeSystemEvent( payload: SystemMessagePayload, currentPubkey: string | undefined, profiles: UserProfileLookup | undefined, - personaLookup?: Map, - agentPubkeys?: ReadonlySet, ): SystemMessageDescription | null { - const isTargetAgent = - payload.target !== undefined && - (agentPubkeys?.has(normalizePubkey(payload.target)) === true || - profiles?.[normalizePubkey(payload.target)]?.isAgent === true || - personaLookup?.has(normalizePubkey(payload.target)) === true); const actorLabel = resolveDisplayLabel( payload.actor, currentPubkey, @@ -214,7 +190,7 @@ function describeSystemEvent( {actorLabel} ); const targetName = ( - + {targetLabel} ); @@ -275,17 +251,12 @@ function describeSystemEvent( export const SystemMessageRow = React.memo(function SystemMessageRow({ message, currentPubkey, - agentPubkeys, profiles, - personaLookup, onToggleReaction, }: { message: TimelineMessage; currentPubkey?: string; - agentPubkeys?: ReadonlySet; profiles?: UserProfileLookup; - /** Map from lowercase pubkey → persona display name for bot members. */ - personaLookup?: Map; onToggleReaction?: ( message: TimelineMessage, emoji: string, @@ -315,8 +286,6 @@ export const SystemMessageRow = React.memo(function SystemMessageRow({ payload, currentPubkey, profiles, - personaLookup, - agentPubkeys, ); if (!description) { return null; @@ -340,18 +309,20 @@ export const SystemMessageRow = React.memo(function SystemMessageRow({ targetPubkey={payload.target} />
-
-
+ + {description.title} -
+ + +
+

+ {description.action} +

-

- {description.action} -

{footer} diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index ba30a727a0..aaa551879f 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -736,8 +736,7 @@ function createMarkdownComponents( ): Components { const paragraphClassName = "leading-6"; const listItemClassName = "my-1 [&_p]:inline"; - const listClassName = - "space-y-1 pl-6 marker:text-muted-foreground"; + const listClassName = "space-y-1 pl-6 marker:text-muted-foreground"; return { a: ({ children, href, ...props }) => { @@ -1033,10 +1032,7 @@ function createMarkdownComponents( } return ( - + {children} ); From 30c3b14f82ef438bde57d6696375fb036f7d2596 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 15 Jun 2026 14:39:13 -0700 Subject: [PATCH 11/20] fix(desktop): align message text stack with avatar height - Increase timeline message avatars to the 10 spacing token so one-line messages have a clearer visual anchor - Replace the previous negative text-column offset with an explicit gap between the header and body - Set header author and timestamp line-height to leading-4 so the header plus one body line aligns with the avatar height - Apply the same header/body gap treatment to system message rows for consistent vertical rhythm --- desktop/src/features/messages/ui/MessageHeader.tsx | 4 ++-- desktop/src/features/messages/ui/MessageRow.tsx | 10 +++++----- desktop/src/features/messages/ui/MessageTimestamp.tsx | 2 +- desktop/src/features/messages/ui/SystemMessageRow.tsx | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/desktop/src/features/messages/ui/MessageHeader.tsx b/desktop/src/features/messages/ui/MessageHeader.tsx index 9439f39be6..c9a5f219ba 100644 --- a/desktop/src/features/messages/ui/MessageHeader.tsx +++ b/desktop/src/features/messages/ui/MessageHeader.tsx @@ -14,7 +14,7 @@ export function MessageHeaderRow({ return (
@@ -39,7 +39,7 @@ export function MessageAuthorText({ return ( {avatarNode}
)} -
+
{message.pubkey ? (