From 07e3b0243a0c77cadbb29db871abb730d5e0ceff Mon Sep 17 00:00:00 2001 From: ss Date: Sat, 2 May 2026 01:20:38 +0200 Subject: [PATCH 01/11] UI polish across settings, composer, chat; web context menu fallback; remove TODO.md - Model picker, provider settings, traits, badges, timestamps, branch/workspace pickers - Bundled fonts; context menu fallback reliability; DONE.md changelog - Delete TODO.md; server/provider contract and text-generation related updates Co-authored-by: Cursor --- DONE.md | 344 ++++++++++++++ TODO.md | 13 - apps/desktop/src/clientPersistence.test.ts | 1 + apps/desktop/src/main.ts | 6 + .../OrchestrationEngineHarness.integration.ts | 1 + .../src/git/Layers/ClaudeTextGeneration.ts | 30 +- .../src/git/Layers/CodexTextGeneration.ts | 33 +- .../src/git/Layers/CursorTextGeneration.ts | 33 +- apps/server/src/git/Layers/GitManager.test.ts | 20 + .../src/git/Layers/OpenCodeTextGeneration.ts | 32 +- .../src/git/Layers/TextGenerationLive.test.ts | 2 + .../src/git/Layers/TextGenerationLive.ts | 9 +- apps/server/src/git/Prompts.ts | 50 ++ .../server/src/git/Services/TextGeneration.ts | 22 + apps/server/src/git/Utils.ts | 27 ++ .../Layers/ProviderCommandReactor.test.ts | 10 + .../src/provider/Layers/OpenCodeAdapter.ts | 2 +- .../src/provider/Layers/OpenCodeProvider.ts | 4 +- apps/server/src/server.test.ts | 9 + apps/server/src/ws.ts | 17 + apps/web/components.json | 3 +- apps/web/index.html | 7 +- apps/web/package.json | 2 + apps/web/src/components/BranchToolbar.tsx | 47 +- .../BranchToolbarBranchSelector.tsx | 174 +++++-- .../BranchToolbarEnvModeSelector.tsx | 37 +- apps/web/src/components/ChatView.tsx | 20 + .../src/components/CommandPalette.logic.ts | 2 + apps/web/src/components/CommandPalette.tsx | 41 +- .../src/components/CommandPaletteResults.tsx | 12 +- apps/web/src/components/GitActionsControl.tsx | 13 +- apps/web/src/components/Sidebar.tsx | 31 +- .../src/components/ThreadTerminalDrawer.tsx | 3 +- apps/web/src/components/chat/ChatComposer.tsx | 27 +- apps/web/src/components/chat/ChatHeader.tsx | 22 +- .../chat/CompactComposerControlsMenu.tsx | 20 +- .../components/chat/ComposerCommandMenu.tsx | 57 +-- .../chat/ComposerPendingUserInputPanel.tsx | 129 +++-- .../chat/ComposerPrimaryActions.tsx | 4 +- .../src/components/chat/MessageCopyButton.tsx | 4 +- .../components/chat/MessagesTimeline.test.tsx | 29 +- .../src/components/chat/MessagesTimeline.tsx | 445 ++++++++++++------ apps/web/src/components/chat/ModelListRow.tsx | 134 +++--- .../components/chat/ModelPickerContent.tsx | 206 ++++---- .../components/chat/ModelPickerSidebar.tsx | 351 +++++++------- apps/web/src/components/chat/OpenInPicker.tsx | 7 +- .../components/chat/ProviderInstanceIcon.tsx | 14 +- .../chat/ProviderModelPicker.browser.tsx | 66 ++- .../components/chat/ProviderModelPicker.tsx | 90 ++-- .../components/chat/SelectedModelBadge.tsx | 23 + apps/web/src/components/chat/TraitsPicker.tsx | 280 +++++++---- .../components/chat/composerProviderState.tsx | 10 +- .../src/components/chat/providerIconUtils.ts | 46 +- apps/web/src/components/color-selector.tsx | 101 ++++ .../settings/AddProviderInstanceDialog.tsx | 60 +-- .../settings/ProviderAccentColorPicker.tsx | 278 +++++++++++ .../settings/ProviderInstanceCard.tsx | 273 ++++++----- .../settings/ProviderModelsSection.tsx | 23 +- .../components/settings/SettingsPanels.tsx | 85 ++-- .../src/components/settings/providerStatus.ts | 49 +- .../components/settings/settingsLayout.tsx | 13 +- apps/web/src/components/ui/autocomplete.tsx | 2 +- apps/web/src/components/ui/blur-reveal.tsx | 44 ++ apps/web/src/components/ui/button.tsx | 4 +- apps/web/src/components/ui/combobox.tsx | 2 + apps/web/src/components/ui/command.tsx | 2 +- apps/web/src/components/ui/dialog.tsx | 2 +- apps/web/src/components/ui/kbd.tsx | 65 ++- apps/web/src/components/ui/menu.tsx | 50 +- apps/web/src/components/ui/select.tsx | 11 +- apps/web/src/components/ui/tabs.tsx | 129 +++++ apps/web/src/components/ui/toggle.tsx | 4 +- apps/web/src/contextMenuFallback.ts | 193 +++++++- apps/web/src/environmentApi.ts | 1 + .../environments/runtime/connection.test.ts | 1 + .../src/hooks/useToolWorkLogFriendlyLine.ts | 167 +++++++ apps/web/src/index.css | 21 +- apps/web/src/lib/gitStatusState.test.ts | 1 + apps/web/src/lib/toolWorkLogSummaryCache.ts | 85 ++++ apps/web/src/localApi.test.ts | 3 + apps/web/src/main.tsx | 3 + apps/web/src/rpc/wsRpcClient.ts | 3 + apps/web/src/session-logic.test.ts | 157 ++++++ apps/web/src/session-logic.ts | 164 ++++++- apps/web/src/timestampFormat.ts | 69 +++ apps/web/test/wsRpcHarness.ts | 9 +- bun.lock | 6 + packages/contracts/src/git.ts | 20 + packages/contracts/src/ipc.ts | 9 + packages/contracts/src/rpc.ts | 11 + packages/contracts/src/settings.ts | 2 + 91 files changed, 4021 insertions(+), 1122 deletions(-) create mode 100644 DONE.md delete mode 100644 TODO.md create mode 100644 apps/web/src/components/chat/SelectedModelBadge.tsx create mode 100644 apps/web/src/components/color-selector.tsx create mode 100644 apps/web/src/components/settings/ProviderAccentColorPicker.tsx create mode 100644 apps/web/src/components/ui/blur-reveal.tsx create mode 100644 apps/web/src/components/ui/tabs.tsx create mode 100644 apps/web/src/hooks/useToolWorkLogFriendlyLine.ts create mode 100644 apps/web/src/lib/toolWorkLogSummaryCache.ts diff --git a/DONE.md b/DONE.md new file mode 100644 index 0000000000..4eeda47589 --- /dev/null +++ b/DONE.md @@ -0,0 +1,344 @@ +# DONE + +Temporary notes for the UI nitpick and issue PR. + +## Issues + +### Open picker shortcut styling + +- Issue spotted: The preferred editor shortcut in the Open picker dropdown rendered as muted text (`Ctrl+O`) instead of using the shared keybinding pill style used by Search (`Ctrl+K`). +- Applied fix: Updated `apps/web/src/components/chat/OpenInPicker.tsx` to render the shortcut with the shared `Kbd` component while preserving right-aligned menu layout spacing. + +### Runtime Google Fonts dependency + +- Issue spotted: The web app loaded DM Sans from Google Fonts at runtime, which adds an external network dependency and is less reliable for packaged desktop/offline usage. +- Applied fix: Added Fontsource packages for DM Sans and JetBrains Mono, imported the bundled font CSS from `apps/web/src/main.tsx`, removed the Google Fonts links from `apps/web/index.html`, updated the app and Tailwind sans stacks to use Fontsource's `DM Sans Variable` family before system fallbacks, added JetBrains Mono to the mono fallback stacks after SF Mono, and updated the Tailwind `--font-mono` token so `font-mono` UI uses the bundled fallback before Consolas. + +### Dropdown chevron placement + +- Issue spotted: Compact dropdown triggers placed chevrons slightly too far left from the rounded right edge, including the composer model picker and reasoning select controls. +- Applied fix: Added a small negative end margin to chevrons in the shared `SelectTrigger`/`SelectButton` paths and matching compact composer picker triggers so the icon aligns visually with the trigger's right padding. Removed broad `[&_svg]:mx-0` resets from custom picker buttons because they overrode the chevron's own end margin and caused the bad placement to persist. + +### Text generation model row clarity + +- Issue spotted: The text generation model setting duplicated provider/sub-provider text in the model trigger and exposed an `agent` selector (`Build`) that is meaningful for interactive agent sessions but confusing for generated commit messages and PR text. +- Applied fix: Changed model trigger labels to show the model name only while preserving provider identity through the icon and picker details, stripped duplicated sub-provider prefixes from model names, and hid the `agent` option from the text generation traits control so the row focuses on model and reasoning choices. + +### Text generation model description layout shift + +- Issue spotted: The text generation model description could resolve to either one or two lines depending on how much width the adjacent dropdowns consumed, causing the row height to shift when the controls changed size. +- Applied fix: Added an opt-in two-line description minimum to the shared settings row layout using the resolved line-height unit, then enabled it for the text generation model row so the helper text always reserves a stable two-line block. + +### Model picker rail selected-item shape + +- Issue spotted: The model picker provider rail selected state rounded its right corners inside the narrow left pane, exposing the darker rail background beside the content divider and making the selected favorites button look clipped. +- Applied fix: Squared the selected rail button's right edge, extended its selected background to the divider, and reduced the rail's top inset so the favorites star aligns vertically with the search header. + +### Model picker visual noise and OpenCode source split + +- Issue spotted: The model picker mixed rail separators, per-row divider lines, left-aligned favorite stars, and duplicated OpenCode sub-provider labels, making the popup noisy and making OpenCode Go/Zen model sources hard to scan. +- Applied fix: Reworked the shared model picker popup in the same footprint: removed the rail favorites separator, aligned the favorites rail item with the search header, removed model-row dividers, moved favorite stars to the right edge of each row, added a neutral `Selected` badge and stronger selected-row highlight, normalized OpenCode row provider labels to `OpenCode Go` or `OpenCode Zen`, and added Go/Zen tabs beside the search input to filter OpenCode models. + +### Model picker scroll lock and selected-row state + +- Issue spotted: The settings page could still scroll while the model picker was open, the sidebar still inherited rounded clipping from the shared scroll area, and the currently selected model used both a badge and a row background highlight. +- Applied fix: Locked document scrolling while the shared model picker is open, replaced the sidebar scroll area with a plain overflow-hidden rail inside the already rounded picker container, and removed the selected-row background/ring so selected state is represented by the neutral badge only. + +### Model picker background wheel scroll + +- Issue spotted: The settings panel could still wheel-scroll behind the model picker because it is an internal scroll container, so locking the document body was not enough. +- Applied fix: Added a model-picker-open wheel/touch guard that permits scrolling inside the picker content but prevents background scroll outside it, and nudged the model-row favorite button slightly farther right for better edge alignment. + +### Provider status dot tooltip + +- Issue spotted: Provider status dots communicated state through color only, with no hover text explaining whether a provider was ready, unavailable, disabled, or needed attention. +- Applied fix: Added short status tooltips to the provider icon/status-dot area in provider settings, using compact labels such as `Authenticated`, `Unauthenticated`, `Missing Binary`, `Needs Attention`, `Unavailable`, `Disabled`, or `Checking`. + +### Provider authenticated description noise + +- Issue spotted: Authenticated provider rows used clipped fragments separated by punctuation, repeated subscription text awkwardly, and kept the status tooltip hover target very small. +- Applied fix: Increased the provider status/icon hover target and changed authenticated provider descriptions to read as natural language, e.g. `Authenticated as {email} using your Claude Pro subscription`, with `subscription` lowercased. + +### Provider environment variables nesting + +- Issue spotted: Provider environment variables rendered as nested rounded row cards with native checkboxes and repeated `Sensitive` row labels, making the dropdown feel visually heavy and inconsistent with the shared UI kit. +- Applied fix: Reworked the environment variables editor into a single shadcn-style table container with alternating row backgrounds, shared `Checkbox` controls under a `Sensitive` header, and no repeated sensitivity label or per-row card shell. Provider status tooltips now open with a 100ms delay. + +### Model picker accent color repetition + +- Issue spotted: Accent-colored provider instances showed both a sidebar badge and a repeated accent dot on every model row, so the model list carried the same color signal over and over. +- Applied fix: Kept accent color as a compact color-only badge in the model picker rail and removed the per-row accent dot from model provider captions. + +### Model picker selected rail strip + +- Issue spotted: The selected provider rail item extended a different background color behind the blue selection hint, making the right edge look like a mismatched extra sliver. +- Applied fix: Changed the selected rail extension to use the same muted background as the left pane so only the selection hint stands out, while keeping the selected provider button's right corners rounded correctly. + +### Model picker OpenCode tab layout shift + +- Issue spotted: Switching to or from OpenCode mounted/unmounted the Go/Zen tab switch beside search, slightly changing the header layout and causing a small picker shift. +- Applied fix: Gave the search header row a stable minimum height while letting the search input fill the available width when the OpenCode tabs are not present. + +### Provider warning description verbosity + +- Issue spotted: Provider rows with warning/error details printed the full diagnostic inline, making the provider list noisy and forcing long wrapped descriptions. +- Applied fix: Kept the row body to the short status headline, e.g. `Needs attention`, and moved the detailed diagnostic into a circle-question tooltip beside the headline. + +### Provider status dot outline color + +- Issue spotted: Provider status dots used the global page background for their clipping ring, which looked like a harsh outline inside the provider card. +- Applied fix: Matched the status-dot ring to the card surface color so the indicator clips cleanly without a visible mismatched halo. + +### OpenCode connected provider copy + +- Issue spotted: OpenCode provider status used backend-oriented wording like `upstream providers connected through OpenCode`, which was technically accurate but unclear in the settings UI. +- Applied fix: Changed the OpenCode success message to natural user-facing copy and made the visible settings row use it instead of the generic `Authenticated · opencode` headline. + +### Provider status punctuation + +- Issue spotted: Provider status rows mixed punctuated and unpunctuated messages, making the provider list inconsistent with surrounding settings copy. +- Applied fix: Normalized provider summary headlines/details to end with terminal punctuation and added punctuation to the authenticated provider sentence. + +### Provider accent color selector + +- Issue spotted: Provider accent color controls used a bespoke swatch picker plus a native color input that did not match the app's shadcn-style controls. +- Applied fix: Added Spell UI's `ColorSelector`, replaced provider accent swatches with the imported component, and added a matching custom-color swatch that opens a zero-padding popover containing only the native color picker. +- Follow-up fix: Replaced the native color input with a shadcn-style custom picker panel that opens directly from the custom swatch, keeps the popover flush, and omits opacity and extra fields. +- Follow-up fix: Moved the custom color swatch back to the first position, made it use the selected accent fill and selected ring styling, and replaced the shifting text clear action with a reserved X icon button. +- Follow-up fix: Reduced the custom swatch eyedropper to a quieter translucent icon and removed accent initials from provider status badges so the color marker stays visual-only. +- Follow-up fix: Softened the custom swatch eyedropper icon to `text-foreground/25` so it stays visible without overpowering the selected color. +- Follow-up fix: Removed initials from accent badges in the composer model dropdown trigger as well, preserving initials only for duplicate-provider disambiguation without a custom accent. +- Follow-up fix: Normalized accent swatch selected rings to use the card surface as the offset color and removed the extra neutral ring class so custom and preset swatches share the same ring treatment. +- Follow-up fix: Replaced CSS-state-based indicator clipping with explicit component values: composer triggers use the input surface, provider settings use the card surface, and model picker rail items use JS hover/selected state to choose the normal, hovered, or selected surface. +- Follow-up fix: Gave hovered/focused provider rail state priority over selected state so selected items that darken on hover clip indicators against the highlighted surface. + +### Environment variable table alignment + +- Issue spotted: The Sensitive checkbox was centered in its table column, making it feel detached from the column header. +- Applied fix: Left-aligned the Sensitive header and checkbox cell so the control starts at the same column edge. + +### Provider status text alignment + +- Issue spotted: Provider status copy started at the card edge while the provider title started after the status/icon cluster, making the paragraph look misaligned. +- Applied fix: Added matching left padding to provider status copy so it aligns with the provider title text. +- Follow-up fix: Flattened authenticated provider status copy into normal paragraph text with inline spans and baseline-aligned email reveal control so wrapped lines align naturally. +- Follow-up fix: Middle-aligned the status help icon within the status text line so Disabled, Needs attention, and similar states do not look vertically offset. +- Follow-up fix: Wrapped non-auth provider status text and its help icon in a single inline-flex span so the icon is centered by layout rather than font baseline alignment. +- Follow-up fix: Replaced the filled question-mark status detail icon with a clickable outlined info button that opens the provider detail popup on click. +- Follow-up fix: Restyled the provider status info button to match model-row info buttons and changed provider model detail controls from hover tooltips to click-open popovers. +- Follow-up fix: Removed the redundant `hidden` text tag from model rows because the visibility icon and strikethrough already communicate hidden state. + +### Main model picker selected badge + +- Issue spotted: The selected-model badge styling was local to the model row instead of being a named shared treatment, making it easy for main-page and settings-launched picker states to drift. +- Applied fix: Extracted the neutral `Selected` badge into a shared `SelectedModelBadge` component and used it from the main model picker row. +- Follow-up fix: Removed the left checkmark from the main-page access dropdown and compact access menu, moved selected state into the option header with the shared `Selected` badge, and switched the main composer model selector trigger to the same outlined model-picker treatment used in Settings. +- Follow-up fix: Reverted the composer trigger styling change and removed the composer-only locked-provider model picker layout so the main page opens the same redesigned rail/search/list picker shape as Settings. +- Follow-up fix: Added scoped-rail top padding when favorites are hidden and normalized provider rail button radii so a single locked provider icon no longer hits the picker roof or looks squared off. +- Follow-up fix: Trimmed the scoped single-provider rail top padding by one Tailwind spacing unit so the lone OpenCode icon sits visually centered. + +### Composer trait dropdown split + +- Issue spotted: The composer traits control combined reasoning and fast mode into one dropdown/trigger (`Medium · Fast`), making two independent settings feel like one compound option. +- Applied fix: Split the full composer traits picker into one dropdown per visible trait while reusing the shared menu body/update logic, and added a brain icon to reasoning triggers plus a lightning icon to fast/speed triggers. + +### OpenCode interaction mode toggle consistency + +- Issue spotted: Claude/Codex/Cursor exposed plan vs. build through a Build/Plan toggle button, but OpenCode hid that toggle and exposed its `agent` descriptor as a traits dropdown instead, leading to inconsistent composer affordances across providers. The active Plan state also lacked a distinct visual cue. +- Applied fix: Flipped OpenCode's `showInteractionModeToggle` to `true` so it uses the same Build/Plan toggle as other providers, hid the `agent` descriptor from the composer trait menus (they're already plan-controlled by the toggle), wired `interactionMode === "plan"` straight to the OpenCode adapter's `activeAgent`, and updated the toggle to swap to a `PencilRulerIcon` blueprint icon with a blue-tinted background/text when Plan is active. + +### Traits dropdown selected/default badges + +- Issue spotted: Traits dropdowns (Reasoning, Fast/speed, Context Window, etc.) used a leading checkmark indicator for the active option and an inline `(default)` text suffix, which did not match the neutral `Selected` badge used by the runtime mode (Supervised/Auto-accept/Full access) menu and put the default hint inline with the option label. +- Applied fix: Hid the radio checkmark in traits dropdowns and rendered the shared `SelectedModelBadge` next to the active option, and replaced the inline `(default)` text with a new neutral `DefaultBadge` so option rows use consistent badge chrome. + +### Selected badge blue tint + +- Issue spotted: The shared `SelectedModelBadge` rendered as a neutral muted chip in some surfaces (runtime mode dropdown, model picker rows) while Plan mode used a blue-tinted active treatment, so the "selected" signal was not consistent across composer menus. +- Applied fix: Updated `SelectedModelBadge` to use the same blue-tinted border/background/text as Plan mode in all call sites (runtime mode menu, traits dropdowns, model list rows, compact composer controls), removed the per-call `tone` prop since every consumer wants the blue treatment. + +### Default and Selected badges stack instead of replacing + +- Issue spotted: When a traits option was both the provider default and the currently selected value, only the blue `Selected` badge rendered — the `Default` badge was hidden because the chooser fell through an either/or branch, hiding the "this is also the default" signal. +- Applied fix: Rendered the `DefaultBadge` and `SelectedModelBadge` independently so both badges show side-by-side when an option is the default and the selected value at the same time. + +### Provider accent badge clipped on icon + +- Issue spotted: The accent-color circle and status dot rendered by `ProviderInstanceIcon` were positioned with negative offsets so they sat outside the icon's own bounding box, but ancestor wrappers in the composer model picker trigger applied `overflow-hidden` for label truncation and clipped those badges off (visible as a missing top of the accent circle in the composer trigger). +- Applied fix: Moved the `overflow-hidden` clipping off the composer model picker trigger button and inner row span (the inner label still has its own `overflow-hidden truncate` so text truncation behavior is unchanged), and added explicit `overflow-visible` plus a small `z-10` to the status dot/accent badge inside `ProviderInstanceIcon` so the indicators sit above neighbor content and aren't clipped by stacking contexts. + +### Branch toolbar trigger icon + +- Issue spotted: The branch picker trigger under the composer (e.g. `main`) only rendered a label and chevron, while its left-hand counterpart (`Current checkout`) had a folder/worktree icon, making the two triggers feel inconsistent. +- Applied fix: Added a leading `GitBranchIcon` to the branch selector trigger so the active branch label sits next to a clear branch glyph and visually matches the workspace selector beside it. + +### Composer focus ring matches input ring + +- Issue spotted: The composer surface used a flat 1px `has-focus-visible:border-ring/45` border swap when the prompt textarea was focused, which looked thin and harsh compared to the thicker `ring-ring/24 ring-[3px]` glow that the shared `Input` component uses on focus. +- Applied fix: Replaced the composer surface's flat border-color swap with the same focus treatment used by the shared input — `ring-ring/24` baseline plus `has-focus-visible:border-ring has-focus-visible:ring-[3px]` — so the prompt textarea picks up the soft, thicker blue-gray ring on focus instead of a flat colored border. + +### Composer stop button uses destructive color + +- Issue spotted: The stop button shown while a prompt is running used hard-coded `bg-rose-500` Tailwind utilities, which read as pink in the app's theme rather than the red used elsewhere for destructive actions. +- Applied fix: Switched the stop button to the theme-aware `bg-destructive` token (with `/90` baseline + solid hover) so it uses the same red as other destructive surfaces and adapts to light/dark themes via the existing `--destructive` variable. + +### User message bubble shape and metadata layout + +- Issue spotted: The user message bubble used `rounded-br-sm` for a small chat-tail effect that still read as a fully rounded corner, the timestamp lived inside the bubble next to hover-only copy/revert actions, and the copy button used the heavier `outline` variant which felt out of place against the bubble's filled background. +- Applied fix: Switched the bottom-right corner to `rounded-br-none` so the chat-tail asymmetry is unmistakable, moved the timestamp paragraph out from inside the bubble into a sibling under it (right-aligned, always visible), and changed the copy + revert buttons to `variant="ghost"` so the hover actions sit on the bubble surface without their own border. + +### User message hover actions outside the bubble + +- Issue spotted: After moving only the timestamp out from the user message bubble, the hover-only copy/revert actions stayed inside, which made the bubble grow on hover and looked mangled because the action row was reserving height inside the bubble whenever it was hovered. +- Applied fix: Promoted the bubble's `group` to the outer column wrapper, moved the entire hover action cluster out alongside the timestamp into a single right-aligned metadata row beneath the bubble, and bumped the timestamp to `text-sm` so it matches the new sibling layout instead of being stuck on a smaller `text-xs` size. + +### Assistant message metadata consistency + +- Issue spotted: The assistant message metadata row used `text-[10px] text-muted-foreground/30` (very small + extremely low contrast), the copy button used `variant="outline"` with custom border/background classes that did not match the user message's ghost copy button, and the order placed the timestamp before the copy button while the user-side row now ordered actions then timestamp. +- Applied fix: Reordered the assistant metadata to match the user side (copy button first, timestamp after), switched the assistant copy button to `variant="ghost"` with no extra border/background overrides, and changed the timestamp typography to `text-sm text-muted-foreground/50` so both sides share consistent metadata size and contrast. + +### Hover-gated metadata row, no nested hover containers + +- Issue spotted: The hover behavior for the user message metadata used a nested wrapper — the outer column was the `group`, and the inner action cluster also gated its own opacity — so the cluster needed both the outer `group:hover` AND its own state to reveal, which made hover feel inconsistent. The user wanted a single hover boundary where the copy button, revert button, and timestamp all fade together, anchored to the message itself. +- Applied fix: Promoted opacity gating to the single metadata-row wrapper (no nested hover divs), so on user messages the entire `[revert] [timestamp] [copy]` row fades in together with the bubble's `group-hover`, and on assistant messages the `[copy] [timestamp]` row fades in together with `group-hover/assistant`. Reordered the user-side metadata so the copy button still sits at the far right of the row. + +### Chat-style relative timestamp formatter + +- Issue spotted: Message timestamps were always rendered as a raw clock time (e.g. `10:00:09`) which made it hard to scan when a message was sent — there was no day-relative context (today vs yesterday vs last week) — and the timestamp paragraph was rendered at `text-muted-foreground/50` while the surrounding ghost copy button used `text-foreground`, so the metadata color did not match the action color even though they live on the same row. +- Applied fix: Added a shared `formatChatTimestamp(isoDate)` utility in `apps/web/src/timestampFormat.ts` that always returns a relative label — `Ns ago` under a minute, `Nm ago` under an hour, `Nh ago` under a day, `a day ago`, `N days ago`, `a week ago` / `N weeks ago`, `a month ago` / `N months ago`, and `a year ago` / `N years ago` for older messages. Wired the new formatter into both the user message metadata row and the assistant `formatMessageMeta` helper (dropping the now-unused `timestampFormat` argument at call sites), and switched both timestamp paragraphs to `text-foreground` so the timestamp color matches the ghost copy button it sits next to. + +### Drop today-clock fallback in chat timestamp + +- Issue spotted: The first version of `formatChatTimestamp` returned the absolute clock time (`10:00`) for messages from today — which is what was visibly rendering for fresh messages — and used `yesterday at {time}` for the previous calendar day. The user wanted purely relative output that scales smoothly from `12s ago` through `43m ago`, `12h ago`, `a day ago`, etc. +- Applied fix: Rewrote `formatChatTimestamp` to always produce a relative label (no absolute-clock fallback): seconds for the first minute, minutes under an hour, hours under a day, then `a day ago` / `N days ago` and the existing week/month/year tiers. Removed the `yesterday at {time}` branch and the `timestampFormat` parameter since the formatter no longer renders any clock components. + +### Chat header project badges and drawer toggles + +- Issue spotted: Outline badges next to the thread title had cramped padding; terminal and diff icon toggles used the default/outline toggle styling instead of ghost chips. +- Applied fix: Tuned header badge layout with `px-2 py-1`, flex centering, `leading-none`, and responsive overrides `sm:h-auto sm:min-h-0 sm:min-w-0` so vertical padding is not negated by the shared `Badge` default size (`h-5.5` / `sm:h-4.5`). +- Applied fix: Added a `ghost` variant to `apps/web/src/components/ui/toggle.tsx` (transparent border/shadow, `data-pressed:bg-accent`) and set both terminal and diff `Toggle`s in `apps/web/src/components/chat/ChatHeader.tsx` to `variant="ghost"`. + +### Command palette folder browse key hints + +- Issue spotted: Footer key hints used raw `Kbd`/`KbdGroup`; `KbdGroup` wrapped non-key content in a `` which is awkward semantically. Add pill repeated the same pattern. +- Applied fix: Introduced `Shortcut` in `apps/web/src/components/ui/kbd.tsx` as a `Kbd` wrapper with `data-slot="shortcut"`. Updated `apps/web/src/components/CommandPalette.tsx` so the footer hint row and the browse Add button use `Shortcut`, with flex `div` wrappers instead of `KbdGroup`. + +### Branch picker row tags as badges + +- Issue spotted: Git branch rows in the branch combobox showed naked lowercase `current` / `remote` (and similar) as muted text on the right; they did not match dropdown badge styling such as the blue `Selected` chip. +- Applied fix: Replaced the right-hand labels in `apps/web/src/components/BranchToolbarBranchSelector.tsx` with shared `Badge` components: **Current** uses the same blue outline treatment as `SelectedModelBadge`; **Remote**, **Worktree**, and **Default** use the neutral outline/muted chip treatment like `DefaultBadge`. +- Follow-up fix: Removed the `border-b` between the branch search field and the list; restructured the popup body with `flex min-h-0 flex-1 flex-col overflow-hidden` so the scrollable list gets a stable max height. Non-virtualized lists keep the existing `ComboboxList` `ScrollArea` `scrollFade` (same mechanism as the model picker list). Virtualized lists (`LegendList`) use a `from-popover` bottom gradient overlay with opacity driven by scroll position (`getScrollableNode` + `syncBranchListScrollChrome`) so more content below is hinted the same way; scroll listeners for infinite branch loading skip the virtualized path when attaching to the wrong node and instead run from `LegendList` `onScroll`. +- Follow-up fix: Branch row uses `flex … justify-between gap-2` with a `min-w-0 flex-1 truncate` branch label and the kind badge. Tightened flush layout: shared `ComboboxItem` used `pe-4` (and list `px-1`), so badges sat inset; branch picker items use `pe-2`, and `ComboboxList` / `ComboboxListVirtualized` get `not-empty:ps-1 not-empty:pe-0` so the right edge matches the scroll viewport; the badge has only its own `ps`/`pe` padding, no margin. + +### Workspace env mode selector matches branch-style badges + +- Issue spotted: The Workspace `Select` (desktop) and mobile `MenuRadioItem` workspace options used the default leading check indicator; selection did not match other menus that use the blue `Selected` chip. +- Applied fix: `BranchToolbarEnvModeSelector` and mobile workspace rows in `BranchToolbar` now pass `hideIndicator` with `ps-2 pe-2`, row layout `justify-between`, and render `SelectedModelBadge` on the active option only (same blue treatment as model/branch pickers). + +### Work group tool-call card styling + +- Issue spotted: The collapsed work / tool timeline card used muted `bg-card/25`, tight padding, all-caps microcopy (`TOOL CALLS (15)`), and a plain “show more” control without a chevron. +- Applied fix: `WorkGroupSection` in `apps/web/src/components/chat/MessagesTimeline.tsx` now uses the same surface tokens as `Textarea` (`rounded-lg border border-input bg-background shadow-xs/5 dark:bg-input/32 not-dark:bg-clip-padding`), `p-4` padding, sentence-case headers (`N tool calls` / `N work log entries`), and chevron icons on the expand/collapse control (`ChevronDownIcon` / `ChevronUpIcon`). Updated `MessagesTimeline.test.tsx` to assert on the new work-log header copy. + +### Pending user input panel (answer dialog) refinements + +- Removed the `ANSWER NEEDED` uppercase header from `apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx`; only the multi-question `N/M` indicator remains when applicable. +- Stacked each option's label and description vertically (label on top, smaller muted description below) instead of inline. +- Moved the digit shortcut (`1`, `2`, `3`, …) from the left of each card to the right end. The existing keyboard-shortcut handler in the same file (digit keys 1–9) was already wired and still triggers the matching option. Selected cards swap the shortcut for a check icon at the same position; both are vertically centered (`items-center`, no top margin). +- Added a fourth `Other` option in the temporary preview at `apps/web/src/components/chat/ChatComposer.tsx` (`TEMP_ALWAYS_SHOW_ANSWER_DIALOG_INPUT`). The panel detects `option.label === "Other"` and renders a transparent text `` in place of the description, using the description string as the placeholder. Key/click events on the input stop propagation so typing does not trigger the digit shortcuts or row selection. +- Made the `Other` card a `