From 610d1ed0dad9daa8d482871fcf0a36ae7476d00a Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Thu, 27 Aug 2026 21:46:40 -0500 Subject: [PATCH] refactor(desktop): give the sidebar row geometry and a bounded cache one owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Row geometry lived inside chrome.tsx as private consts, so anything that wanted to line up with a session row copied the literals instead — the lead cell alone appeared verbatim in six files. Its own comment warns that owning height anywhere else makes rows float 1-2px off sessions, which is exactly what a copy invites. Split the measurements into row-geometry.ts, keep the public names re-exported from chrome.tsx, and put the lead cell and ConnectionGlyph on the plugin SDK so a plugin composes the same box rather than re-deriving it. ConnectionGlyph takes a className now so a caller can tint it without forking the component. LruCache is the same move for a different leak: katex-memo.ts had a private one with a hardcoded ceiling, and the bot rail had two Maps that never evicted. One shared class, exported, katex's twin deleted. --- apps/desktop/src/app/chat/sidebar/chrome.tsx | 62 +++++++---------- .../src/app/chat/sidebar/connection-glyph.tsx | 20 ++++-- .../src/app/chat/sidebar/row-geometry.ts | 55 +++++++++++++++ apps/desktop/src/lib/katex-memo.ts | 37 +--------- apps/desktop/src/lib/lru-cache.test.ts | 68 +++++++++++++++++++ apps/desktop/src/lib/lru-cache.ts | 67 ++++++++++++++++++ apps/desktop/src/sdk/index.ts | 16 ++++- 7 files changed, 246 insertions(+), 79 deletions(-) create mode 100644 apps/desktop/src/app/chat/sidebar/row-geometry.ts create mode 100644 apps/desktop/src/lib/lru-cache.test.ts create mode 100644 apps/desktop/src/lib/lru-cache.ts diff --git a/apps/desktop/src/app/chat/sidebar/chrome.tsx b/apps/desktop/src/app/chat/sidebar/chrome.tsx index 5688736004..3b6d90c9bb 100644 --- a/apps/desktop/src/app/chat/sidebar/chrome.tsx +++ b/apps/desktop/src/app/chat/sidebar/chrome.tsx @@ -9,6 +9,14 @@ import { compactNumber } from '@/lib/format' import { cn } from '@/lib/utils' import { $sidebarRowMeta } from '@/store/layout' +import { + SIDEBAR_ROW_INSET, + SIDEBAR_ROW_LABEL, + SIDEBAR_ROW_LEAD, + SIDEBAR_ROW_MIN_H, + SIDEBAR_ROW_PAD_TRAIL +} from './row-geometry' + // Shared, content-agnostic sidebar chrome — used by both the flat session // sections and the project/workspace tree, so it lives outside either to keep // imports one-directional (no index <-> projects cycle). @@ -18,39 +26,10 @@ export function SidebarSectionMeta({ children }: { children: React.ReactNode }) return {children} } -// ── Row geometry (session row is canonical — everything composes these) ───── -// -// Height lives ONLY on SidebarRowShell (min-h-[1.625rem]). Inset children -// stretch to fill the cell and center content internally — never items-center -// on the shell grid, or short clusters (projects) float 1–2px off sessions. -// -// `rowPadX` is the BODY's padding: the lead's inset, plus the gap the label -// keeps from the actions column, both inside the row's click target. -// `rowPadTrail` is the row's own trailing inset and belongs to the SHELL — the -// only box containing both the actions column AND the card's in-body cluster, -// so one class insets every trailing thing a row can render. Owned anywhere -// else, the age / chips / kebab sit flush on the border box, which is exactly -// where a working row paints its arc (`.arc-row` has zero standoff) — the ring -// ran through the text. - -const rowMinH = 'min-h-[1.625rem]' -const rowPadX = 'pl-2 pr-2' -const rowPadTrail = 'pr-2' -const rowGap = 'gap-1.5' -const rowLead = 'grid size-3.5 shrink-0 place-items-center' -const rowInset = cn(rowPadX, rowGap, 'flex h-full min-w-0 items-center self-stretch py-0.5') -// `truncate` is overflow:hidden. `leading-none` (line-height: 1) makes the -// line box equal the em-square, so glyph ink that sticks out — Segoe UI on -// Windows is ~1.33em — gets shaved. 1.35 leaves room; the shell still owns -// row height, so the extra leading just centers. -export const SIDEBAR_TRUNCATED_LEADING = 'leading-[1.35]' as const -const rowLabel = cn('min-w-0 truncate text-[0.8125rem] text-(--ui-text-secondary)', SIDEBAR_TRUNCATED_LEADING) - -/** Inbox-style card (workspace + age, title + preview, model + size). */ -export const SIDEBAR_ROW_CARD_MIN_H = 'min-h-[3.375rem]' as const - -/** Codicon size in sidebar row leads — matches the file tree (`tree.tsx`). */ -export const SIDEBAR_LEAD_ICON_SIZE = '0.875rem' as const +// Row geometry lives in `row-geometry.ts` — see that file for why each class +// belongs to the box it belongs to. Re-exported here because this module is +// where callers already look for row chrome. +export { SIDEBAR_LEAD_ICON_SIZE, SIDEBAR_ROW_CARD_MIN_H, SIDEBAR_TRUNCATED_LEADING } from './row-geometry' /** Vertical stack of rows (gap-px, single column). */ export function SidebarRowStack({ className, ...props }: React.ComponentProps<'div'>) { @@ -100,7 +79,12 @@ export function SidebarRowShell({ }: React.ComponentProps<'div'> & { actions?: React.ReactNode; actionsClassName?: string }) { return (
{children} @@ -115,12 +99,12 @@ export function SidebarRowShell({ /** Multi-control left cluster (project rows). */ export function SidebarRowCluster({ className, ...props }: React.ComponentProps<'div'>) { - return
+ return
} /** Session row main tap target. */ export function SidebarRowBody({ className, ...props }: React.ComponentProps<'button'>) { - return + return } /** Tappable label — underline/truncate live on the inner span, not the button. */ @@ -132,19 +116,19 @@ export function SidebarRowLink({ }: React.ComponentProps<'button'> & { labelClassName?: string }) { return ( - {children} + {children} ) } /** Fixed leading column (dot, icon, drag handle). */ export function SidebarRowLead({ className, ...props }: React.ComponentProps<'span'>) { - return + return } /** Standard row label typography. */ export function SidebarRowLabel({ className, ...props }: React.ComponentProps<'span'>) { - return + return } /** What a group's sessions add up to, for the Show options that count something. */ diff --git a/apps/desktop/src/app/chat/sidebar/connection-glyph.tsx b/apps/desktop/src/app/chat/sidebar/connection-glyph.tsx index f553d7f6f0..4fd338145a 100644 --- a/apps/desktop/src/app/chat/sidebar/connection-glyph.tsx +++ b/apps/desktop/src/app/chat/sidebar/connection-glyph.tsx @@ -1,11 +1,21 @@ import type { DesktopRegistryConnection } from '@/global' import { Cloud, Monitor, Network, Terminal } from '@/lib/icons' +import { cn } from '@/lib/utils' + +import { SIDEBAR_ROW_LEAD } from './row-geometry' // One glyph per connection kind — device, cloud, network, terminal — shared by -// the statusbar switcher, its menu, and the fleet profile rail so a gateway -// looks the same wherever it is named. Dependency-free on purpose (icons and -// a type only) so light components can use it without pulling in stores. -export function ConnectionGlyph({ connection }: { connection: Pick }) { +// the statusbar switcher, its menu, the fleet profile rail and the Bots rail so +// a gateway looks the same wherever it is named. Dependency-free on purpose +// (icons, a type and class strings) so light components can use it without +// pulling in stores. +export function ConnectionGlyph({ + className, + connection +}: { + className?: string + connection: Pick +}) { const Icon = connection.kind === 'local' ? Monitor @@ -18,7 +28,7 @@ export function ConnectionGlyph({ connection }: { connection: Pick