Skip to content

style(desktop): unified settings vocabulary across every page#4035

Merged
Kitenite merged 73 commits into
mainfrom
settings-cleanup-v1
May 4, 2026
Merged

style(desktop): unified settings vocabulary across every page#4035
Kitenite merged 73 commits into
mainfrom
settings-cleanup-v1

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 4, 2026

Summary

A sweep across every settings page in the desktop app to converge on a single visual vocabulary. No behavioral changes — just style, layout, and copy.

The shared vocabulary:

  • Page wrapper: p-6 max-w-4xl w-full
  • Page title: text-xl font-semibold (sentence case)
  • Section heading: <h3 class="text-sm font-medium">
  • Section description: text-sm text-muted-foreground (was text-xs in places)
  • Borderless rows by default; divide-y lists where appropriate; reserved bordered cards for hero panels (e.g. expanded session table)
  • Sentence case throughout
  • Save-on-blur where validation is client-side (Account name, Project name, Repository URL, Anthropic Advanced overrides, Agent label/command/prompt-args)
  • Click-the-element pattern over hover-revealed Change buttons (Account avatar, Org avatar/slug, V2 project icon)
  • Inverted monochrome pill for paid billing tiers (no gradient/sparkles)

Pages touched

  • Account, Appearance, Terminal (presets + daemon), Links, Models, Organization, Projects (v1 + v2), Hosts, Integrations, Billing, API Keys, V2 Agent
  • Shared SettingsSection helper bumped to text-sm descriptions (affects v1 and v2 — acceptable per v1-sunset)

Highlights worth reviewing

  • V2 project settings: added a Name section with save-on-blur rename (was previously read-only), GitHub "Open" button next to repository URL when it parses, separator-style Delete row instead of a "Danger zone" heading
  • V2 project icon field (merged in from main, restyled): click-the-thumbnail upload (matches AccountSettings avatar), quieter ghost Remove, FaGithub for "Use GitHub icon" so it visually matches the Repository "Open" button
  • Daemon section (Terminal): Update/Force restart buttons hoisted into the section header; chevron disclosure on the status row replaces the Show/Hide sessions button; daemon-unavailable state now uses destructive color; version moved next to the heading; table border dropped
  • Billing: heavy bordered hero cards flattened into borderless rows under a Plan section; small monochrome pill for Pro/Enterprise; BillingDetails: 3 borderless DetailRows with Edit on right; RecentInvoices: flat divide-y list
  • API keys: standardized wrapper, dropped redundant subtitle, swapped Table for divide-y list, dropped bespoke amber warning box
  • Integrations: flat rows with status dot + "Connected to X" / "Not connected" instead of bordered Cards with badges
  • Organization & Account: avatar/logo are now click-the-element file pickers instead of separate Change buttons
  • Projects sidebar: now renders both v1 and v2 in grouped sections (was gating on isV2CloudEnabled and dropping v1)

Notes

Test plan

  • Walk every page in the settings sidebar; confirm headings, descriptions, and row spacing read consistently
  • Rename a v2 project — name updates in title, sidebar, everywhere
  • Paste a github.com URL into v2 Repository row, blur — "Open" button appears, opens the repo
  • V2 project Appearance: click the icon thumbnail to upload; click Remove → ghost button; Use GitHub icon when the repo parses
  • Toggle the daemon sessions disclosure (▶) — table appears/disappears, chevron rotates
  • Daemon-unavailable state shows in destructive (red) color
  • Billing on Free plan shows the Upgrade row; Pro plan shows the inverted pill
  • API key Generate dialog still works, new key dialog still works
  • Integrations rows show correct connected/not-connected status
  • Projects sidebar shows both v1 and v2 groups when present

Summary by CodeRabbit

Release Notes

  • New Features

    • Added sidebar navigation for agent settings with detail view
    • Added terminal preset editor dialog with improved controls
    • Added project name editing in project settings
    • Added status indicators for system permissions
  • UI/Style Improvements

    • Redesigned account, API keys, and billing settings layouts
    • Simplified appearance theme selector interface
    • Improved terminal preset and keyboard shortcut UIs
    • Updated typography and spacing across settings pages
  • Refactor

    • Consolidated settings sidebar components for consistent experience
    • Removed telemetry setting

Kitenite added 30 commits May 3, 2026 22:11
Replace shadcn Card with bordered row-stack pattern (matching 1Code).
Bump avatar to 48px with explicit Change button instead of hover-pencil.
Fix right-column width inconsistency (avatar w-8 vs inputs flex-1) by
giving every row a fixed w-80 control. Wrap Sign out in its own box so
the page reads as two parallel containers instead of card + bare button.
- Sentence case all section headings (Markdown style, Editor font,
  Terminal font, Import theme, Download base file)
- Standardize header rhythm: h3 mb-1 + xs description mb-3
- Declutter Theme header: fold Marketplace and Docs links into a
  natural-language description line
- Markdown: merge the trailing Tufte note into the section description
  so the section ends with the control, not prose
- Font Reset button: ghost text-xs muted -> outline sm so it's
  discoverable next to the controls
- Add aria-label to font size Input and Markdown Select for a11y
Wrap the Outlet in a centered max-width container so every settings page
sits in the middle of the content pane instead of pinned to the left edge
of the sidebar. The pages' own max-w-4xl becomes redundant but harmless;
new pages get centered for free.
- Sentence case headings and button labels (Notification sound, Add /
  Replace custom audio)
- Broaden subtitle so it covers toggle + volume + ringtone, not just
  the picker
- Drop the dangling tip block at the bottom; fold the format hint into
  the section description under Notification sound
- Swap HiPlus -> HiArrowPath when the button means Replace, so the icon
  matches the action
- Drop the decorative speaker icon from the Volume label so it matches
  every other setting label on the page
- Tighten section gap from space-y-8 to space-y-6 to match Behavior
- Drop the ring around selected cards: border-2 + ring-2 + check was
  three signals for one state. Now border-primary + check + a subtle
  footer tint, single-thickness border so cards don't shift on select.
- Promote the play button: bigger (h-9), centered in the preview area
  with a shadow, so it reads as the primary affordance instead of a
  corner hint. Emoji moves below the button as visual identity.
- Move duration inline with the name in the footer (it's metadata, not
  decoration) and drop the floating top-right badge.
- Add aria-label and aria-pressed for keyboard / SR users.
Centered play overlapping the emoji read as a single broken element,
not as two distinct affordances. Restore the corner placement and the
4xl emoji. Keep the other RingtoneCard wins from the previous commit
(no ring on selected, inline duration, aria labels).
Check moves to top-right of the preview area as a small badge so the
emoji area stays uncluttered. Play moves into the footer alongside the
name + duration, where it sits next to the metadata it acts on. Both
affordances now have a single, predictable home each.
Drops the always-hidden ({false && ...}) telemetry toggle and its
useQuery/useMutation/handler plumbing from BehaviorSettings, plus the
matching SETTING_ITEM_ID, variant gate, and search index entry. The
underlying getTelemetryEnabled tRPC procedure stays because
TelemetrySync still consumes it at boot to set the PostHog opt state.
- Match other settings pages: text-xl page title, sentence case
  ("Keyboard shortcuts"), p-6 max-w-4xl w-full wrapper order.
- Drop the inner max-h-[calc(100vh-320px)] scroll: it doubled up with
  the parent settings pane scroll and made the page feel cramped.
  Parent now scrolls naturally.
- Drop the loud uppercase Command/Shortcut column header inside each
  category card -- the rows are clearly label + chip, no header needed.
- Promote "Reset all" from ghost to outline so it reads as a page-level
  action, distinct from the per-row Reset buttons.
- Recording state now visually highlights the active row (subtle
  destructive-tinted bg + ring on the trigger) and changes the trigger
  text from "Recording..." to "Press a key...".
Sentence case headings ("Git & worktrees", "Branch prefix") and tighten
the Branch prefix description so it explains the setting in addition
to showing the preview. WorktreeLocationPicker is left untouched
because v1 ProjectSettings still consumes it.
- Page wrapper max-w-5xl -> max-w-4xl: the layout already caps at 4xl
  so the wider value was silently dead. Match the rest of settings.
- Reset to defaults: ghost -> outline so it reads as a page-level
  action distinct from row-level controls (matches Keyboard's Reset
  all).
- Friendlier zero-state: dashed-border card with a Bot icon, "No
  agents yet" heading, and a hint pointing at the Add menu, instead
  of a single bare line of muted text.
- Loading: skeleton cards that mirror the V2AgentCard layout instead
  of "Loading agent settings...".
- Drop the empty grey square placeholder when a preset has no icon
  (in both the card and the Add-agent dropdown). Empty rectangles
  read as broken; absence reads as intentional.
Card was reading as a "toy" -- big rounded shadow on every row, app
icon at 32px, semibold title, big primary-blue argv/stdin pills.

- Card: rounded-xl + shadow-sm + py-6/gap-6 -> rounded-lg, no shadow,
  no internal gap. Flatter, less floating widget energy.
- Header: text-base semibold title -> text-sm medium title; description
  text-sm -> text-xs muted; preset icon size-8 -> size-7. Header
  padding p-4 -> p-3 to fit the smaller type.
- Drag handle: bare icon with no affordance -> icon in a hover-bg pill
  with a slightly muted default color so it reads as draggable.
- Trash: hover -> destructive tint to make the destructive action read
  as destructive only on intent.
- Padding alignment: expanded CardContent inherited shadcn px-6 (24px)
  while the header was p-4 (16px), so fields were inset further than
  the title above them. Drop CardContent and use plain px-4 pb-4.
- Field grouping: insert a border-t between Label (identity) and the
  three launch fields so the form has a readable two-section structure.
- Field labels shrunk to text-xs and inputs use text-xs font-mono so
  the dense launch settings fit without feeling oversized.
- Argv/stdin: replace two primary-blue pill buttons with a true
  segmented control (single bordered group, accent-tinted selected
  half). Reads as one decision, not two competing buttons.
- Tighten copy: drop "(empty)" placeholder for "--prompt"; compress
  the argv/stdin explanation into one line.
1Code's settings list pattern is one bordered container with divide-y
rows -- no individual cards per item. Agents had 8 separate Cards
floating with space-y-3 between them, which made each row look like
its own widget instead of a list entry.

- V2AgentsSettings: wrap configs in
  `rounded-lg border bg-card divide-y` instead of `space-y-3`.
- V2AgentCard: drop the per-item <Card> wrapper. The row sits flat
  inside the parent container; hover bg lifts via hover:bg-muted/50
  to match 1Code AccountRow.
- Drag state: when isDragging, lift the row via bg-accent/40 + z-10
  so it visually sits above the rest of the list during reorder.

Less floating-widget energy, denser, reads as a settings list.
…elds

The expanded panel previously stacked Label/Input/description vertically
for each field. 1Code's settings forms (model config, account config,
etc.) put label + hint on the left and the control on the right at a
fixed w-80, separated by border-t. Same row vocabulary as the rest of
settings, so the expanded content reads continuous with the page
instead of like a popup form.

- New local AgentField helper renders one row: label + hint left,
  control (w-80, shrink-0) right, separated by border-t.
- All four fields (Label, Command, Prompt-only args, Prompt transport)
  use the new pattern.
- Drop the previous nested grouping divider; row dividers carry the
  separation now.
- Promote field labels back to text-sm so they line up visually with
  the agent's title in the header above.
Same pattern as the v2 agents list: one bordered container with
divide-y rows, no per-item bg, no per-item chrome. Each ringtone is
one row with [emoji] [name + duration on top, description below]
[play button] [check]. Sound selection is a list-of-options task,
not visual comparison, so cards were wasting vertical space and
making each option feel like a widget instead of a list entry.
- Page wrapper max-w-7xl -> max-w-4xl: layout caps at 4xl already, so
  the wider value was dead. Match the rest of settings.
- Sentence case "Terminal presets" / "Add preset" header.
- Drop the orphan "Click a preset row to edit details." hint that sat
  below the table -- click affordance is discoverable from the row
  hover state and editor sheet open.
…down

Replaces the 3-column ThemeCard grid (and split-preview SystemThemeCard) with a
single bordered list: one Theme row with a Select dropdown, and Light/Dark rows
that slide in only when System is selected. Each option shows a small Aa swatch
in the picker. ThemeCard / SystemThemeCard components are no longer needed.
Drop the uppercase column header + striped rows. PresetsTable becomes a
bordered list with divide-y matching Agents/Ringtones. PresetRow shows
name + workspace/tab badges on top, command snippet (truncated, with +N
overflow indicator) below, and a right-side meta column with applies-to
+ mode. Visibility eye and chevron sit at the right edge. QuickAddPresets
becomes a tighter chip strip with rounded-full pills.
V2PresetsSection now wraps header, quick-add chips and the preset list
into one bordered card with divide-y separators, matching the new theme
section. PresetsTable gains a `bordered` prop so v1 keeps its own border.

PresetRow trims the right side: chevron is gone (the whole row already
clicks), applies-to + mode collapse to a single muted line, and the
visibility eye fades in on row hover when the preset is visible.
Renames PresetEditorSheet -> PresetEditorDialog and switches the side
sheet to a max-w-2xl modal Dialog matching 1Code's MCP/agent edit
patterns. The body now uses a 2-column grid for short fields (Name +
Description) and full-width sections for Commands, Applies-to,
Directory, Launch mode, and a bordered Auto-run group.

Drops the custom field/control className overrides, the uppercase
"Advanced" subheading, the question-mark tooltips, and the
LabelWithTooltip helper. Fields use a small Field helper with a
hint underneath instead.
The dialog now uses both halves of its width: left column groups the
recipe (Name, Description, Commands), right column groups the launch
behavior (Applies to, Directory, Launch mode, Auto-run). Drops the
DialogDescription (the title carries enough context), drops the
bordered Auto-run group, and shortens the multi-command launch-mode
labels.
Drops the cluttered two-column form. Width returns to max-w-2xl
(matching 1Code's MCP edit dialog footprint) and the body is single
column:

  - Top: Name, Description, Commands as labeled fields
  - Bottom: bordered settings list with rows for Applies to, Directory,
    Launch mode, plus two Switch rows for the auto-run options

Launch mode now uses a single Select dropdown for both single- and
multi-command cases (replacing the radio group). Auto-run uses Switches
in their own rows instead of two checkboxes packed under one label.
Kitenite added 13 commits May 4, 2026 00:38
The violet/amber gradient + sparkles read as cheap. Replaced with a small high-contrast monochrome pill (Linear/Notion-style) — bg-foreground text-background, uppercase, tight tracking. Single restrained signal that the tier is paid; the Upgrade button goes back to the default primary style.
RepositorySection: drop the Edit/Save/Cancel state machine for save-on-blur on a single Input — type, blur to save, Esc to revert. ProjectLocationSection: drop the redundant 'This device' label column (the section header already covers it); show path or 'Not set up' across the full row with the action button to its right. DeleteProjectSection: lift to label + hint + button so it reads consistently with other settings rows.
…tings

Drawing from 1Code's project tab: add a Name section at the top with save-on-blur rename (was previously read-only via the page header). Repository row now shows a small 'Open' button with the GitHub icon when the URL parses to a github.com remote — opens the repo in the browser. Soften the destructive Delete button to outline + destructive hover so it's clearly dangerous without dominating the page; the AlertDialog still gates the actual delete.
1Code uses text-sm muted descriptions in settings; ours used text-xs which read as cramped. Bump the shared SettingsSection helper to text-sm (touches v1 too — acceptable per v1-sunset). Shorten the v2 project descriptions to label-style phrases ('Display name for this project') instead of conversational ones.
Drop the redundant 'Danger zone' heading — the Delete row already self-describes; replace with a thin border-t separator. Drop the descriptive subtitles on Name and Repository (the labels + placeholders cover it; only Project location keeps its description since state matters). Tighten section spacing space-y-8 → space-y-6.
Drop redundant section descriptions on Label and Launch (the labels + placeholders cover them). Tighten section spacing space-y-10 → space-y-6. Replace the 'Delete this agent' Section heading with a thin border-t separator + label+hint+button row, matching the v2 project Delete pattern.
Members heading text-base → text-sm to match every other settings section. Drop the redundant 'People in your organization who can use this host' description (the heading + table covers it). Surface the 'Only owners can change membership' hint up under the heading when the viewer isn't an owner, instead of as a footer below the table. HostHeader: drop the inline <code> tag for the machine ID, use a font-mono span; trim 'machine ID' to just the ID after the status separator.
Replace the section heading <Label> with an <h3 className='text-sm font-medium'> so it matches every other settings section. Bump the description to text-sm and trim the verbose copy ('Owns every PTY session and survives app restarts.' instead of two sentences explaining what a PTY is). Status row, action buttons, monochrome 'Update available' pill, and AlertDialogs all already match the vocabulary — left alone.
…ilable state

Drop the manual Refresh button — refetchOnWindowFocus + the 5s poll while the list is open already cover this. Replace the 'Show sessions / Hide sessions' action button with a chevron disclosure on the status row itself: chevron + dot + count becomes one clickable element when there are sessions to show. Color-code the daemon-unavailable case with destructive (red dot + destructive text) so it reads as a problem rather than a benign 'no sessions' state.
Update daemon and Force restart move from a row below the status into the right side of the section header — same pattern as Hosts > Members + Add member button. Frees up vertical space and reads top-down: heading + actions, then status + disclosure, then expanded session table.
items-center centers each child's box vertically — but the chevron + dot inside the button have different box metrics than the plain font-mono version span next to it, so the text baselines end up offset. items-baseline aligns the actual text baselines so '1 session running', 'v0.1.4', and the 'Update available' pill all sit on the same line.
…le border

Version moves from the status row up next to 'Manage daemon' as inline metadata (small font-mono, normal weight, muted) — version is a property of the daemon itself, not part of session state. Drop the rounded-md border wrapping the expanded sessions table; the table sits directly in the section without a contained box.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30006c53-f1ef-4516-a9a5-f4eadcaa4fb5

📥 Commits

Reviewing files that changed from the base of the PR and between e2b2b0d and cc32565.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx

📝 Walkthrough

Walkthrough

Adds migration run-time tracking and event hooks; introduces a reusable SettingsListSidebar and SettingRow; refactors many settings UIs (agents, themes, terminal presets, account, billing, models, projects), replaces preset sheets with dialogs, removes legacy theme components, and adjusts layout/styling across settings.

Changes

Migration Tracking

Layer / File(s) Summary
Core Tracking Logic
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts
Added localStorage keying, persistLastRunAt(organizationId), exported readLastMigrationRunAt(organizationId), and V1_MIGRATION_LAST_RUN_AT_EVENT; persistLastRunAt invoked after successful migration.
Hook Re-exports
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/index.ts
Re-exported readLastMigrationRunAt and V1_MIGRATION_LAST_RUN_AT_EVENT.
Experimental Settings Integration
apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx
Added useLastMigrationRunAt() hook to read stored last-run time, listen for V1_MIGRATION_LAST_RUN_AT_EVENT, and refresh relative-time display every minute.

Settings UI & Layout Refactors

Layer / File(s) Summary
Sidebar Infrastructure
apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsListSidebar/SettingsListSidebar.tsx, .../index.ts
Added generic SettingsListSidebar<T> with search/filtering, grouped rows, empty/no-match labels; exported settingsListItemClass() and SettingsListGroup type.
Agents: Data & Wiring
apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx
Introduced selectedAgentId selection state, auto-select logic, selection updates on add/reset, and two-column layout wiring (sidebar + detail).
Agents: Sidebar & Rows
.../AgentsSettingsSidebar/AgentsSettingsSidebar.tsx, .../index.ts
New AgentsSettingsSidebar with dnd-kit drag-and-drop, add-preset dropdown, reset action, and AgentSidebarRow drag-handle wiring.
Agents: Detail
.../AgentDetail/AgentDetail.tsx, .../AgentDetail/index.ts
Added AgentDetail component for edit/delete of agent configs with React Query mutations, input parsing/validation, and toast error handling.
Agents: Legacy Removal
.../V2AgentCard/*
Removed V2AgentCard component and its re-export; replaced by sidebar + AgentDetail.
Themes: UI Replace
apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx
Removed card-based theme components; introduced dropdown/select-based theme picker with ThemeSwatch, ThemeOptionRow, ThemeRow, and grouped option lists (light/dark/system/custom).
Themes: Legacy Component Removal
.../SystemThemeCard/*, .../ThemeCard/*
Deleted SystemThemeCard and ThemeCard modules and their barrel re-exports.
Terminal Presets: Editor Dialog
.../PresetEditorDialog/PresetEditorDialog.tsx, .../index.ts
Added PresetEditorDialog modal: workspace-origin resolution, workspace-relative path conversion, directory validation via TRPC, dynamic launch-mode UI, and full edit controls.
Terminal Presets: Project Targeting
.../PresetEditorDialog/components/ProjectTargetingField/ProjectTargetingField.tsx
Added ProjectTargetingField for scoping presets to all or specific projects with searchable popover and selection guards.
Terminal Presets: Sheet Removal
.../PresetEditorSheet/*
Removed PresetEditorSheet and its ProjectTargetingField variant; dialog replaces sheet-based editor.
Terminal Presets: Rows & Table
.../PresetRow/PresetRow.tsx, .../PresetsTable/PresetsTable.tsx
PresetRow simplified (no isEven), computes single commandSummary, uses dedicated drag/visibility buttons; PresetsTable gained optional bordered prop to control outer border/rounding.
Settings List Consumers
apps/desktop/src/renderer/routes/_authenticated/settings/hosts/.../HostsSettingsSidebar.tsx, apps/desktop/src/renderer/routes/_authenticated/settings/projects/.../ProjectsSettingsSidebar.tsx
Replaced custom sidebar/search implementations with SettingsListSidebar usage and unified group/row filtering rendering.
Account Settings
apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx, .../ProfileSkeleton/ProfileSkeleton.tsx
Introduced local SettingRow helper, switched profile/sign-out UI to row layout; simplified profile skeleton markup.
Models & Config Row
.../ModelsSettings/ModelsSettings.tsx, .../ConfigRow/ConfigRow.tsx, .../SettingsSection/SettingsSection.tsx
Refactored provider advanced controls (Anthropic advanced saved on blur), updated ConfigRow to accept htmlFor and use Label, and adjusted section header typography/layout.
Billing: Consolidation
.../BillingOverview/*
Replaced card sections with DetailRow pattern, simplified CurrentPlanCard/UpgradeCard/RecentInvoices markup and typography.
Permissions
apps/desktop/src/renderer/routes/_authenticated/settings/permissions/components/PermissionsSettings/PermissionsSettings.tsx
Added StatusBadge for Granted/Not granted/Unknown, PermissionRowSkeleton for loading, and updated action copy to "Open settings".
API Keys / Integrations / Ringtones / Keyboard / Organization / Layout
multiple files under settings/*
UI refactors: API keys list → rows, Integrations → rows with status dot, Ringtones → row list, Keyboard layout tweaks, Organization settings row helper, widespread spacing, casing and centering adjustments, and usesInnerSidebar heuristic in settings layout.
Behavior / Search Catalog
apps/desktop/src/renderer/routes/_authenticated/settings/behavior/..., .../settings-search/settings-search.ts
Removed telemetry setting plumbing and removed BEHAVIOR_TELEMETRY from settings catalog and visibility.

Sequence Diagrams

sequenceDiagram
    participant User
    participant V2AgentsSettings
    participant AgentsSettingsSidebar
    participant AgentDetail
    participant HostService

    User->>V2AgentsSettings: Load agents page
    V2AgentsSettings->>V2AgentsSettings: Fetch configs via query
    V2AgentsSettings->>V2AgentsSettings: Auto-select first agent
    V2AgentsSettings->>AgentsSettingsSidebar: Render sidebar with configs

    User->>AgentsSettingsSidebar: Drag agent to reorder
    AgentsSettingsSidebar->>V2AgentsSettings: Call onReorder(newIds)
    V2AgentsSettings->>HostService: Reorder configs mutation

    User->>AgentsSettingsSidebar: Click add agent
    AgentsSettingsSidebar->>V2AgentsSettings: Call onAddAgent(presetId)
    V2AgentsSettings->>HostService: Create new config
    V2AgentsSettings->>V2AgentsSettings: Select newly added agent

    User->>AgentsSettingsSidebar: Click agent to select
    AgentsSettingsSidebar->>V2AgentsSettings: Call onSelectAgent(id)
    V2AgentsSettings->>AgentDetail: Render detail for selected agent

    User->>AgentDetail: Edit label and blur
    AgentDetail->>HostService: Patch agent config
    AgentDetail->>User: Toast success/error
Loading
sequenceDiagram
    participant User
    participant PresetEditorDialog
    participant ElectronTRPC
    participant ProjectTargetingField

    User->>PresetEditorDialog: Open edit dialog
    PresetEditorDialog->>ElectronTRPC: Fetch origin workspace
    ElectronTRPC-->>PresetEditorDialog: Workspace metadata

    User->>PresetEditorDialog: Change directory
    PresetEditorDialog->>ElectronTRPC: selectDirectory()
    ElectronTRPC-->>PresetEditorDialog: Selected path
    PresetEditorDialog->>ElectronTRPC: getDirectoryStatus()
    ElectronTRPC-->>PresetEditorDialog: Is valid directory

    User->>ProjectTargetingField: Switch to specific projects
    ProjectTargetingField->>ProjectTargetingField: Show project popover
    User->>ProjectTargetingField: Select/deselect projects
    ProjectTargetingField->>PresetEditorDialog: onChange(projectIds)

    User->>PresetEditorDialog: Save preset
    PresetEditorDialog->>ElectronTRPC: Update preset config
    ElectronTRPC-->>PresetEditorDialog: Success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through settings, tidy and new,
Sidebars sorted, presets in view,
Agents split to list and detail too,
Themes now pickable from a dropdown crew,
A cheerful thump — the rabbit says woo-hoo!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch settings-cleanup-v1

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Greptile Summary

This PR is a broad visual-vocabulary sweep across all settings pages — standardising padding, typography, layout, and copy with no intended behavioral changes. The main additions are: a new SettingsListSidebar generic component, V2 agent settings split into a sidebar + detail panel, a save-on-blur rename for V2 projects, a GitHub "Open" button in the repository row, and a chevron-based session disclosure in the daemon section.

  • P1 — AgentsSettingsSidebar.tsx: The drag-handle is rendered as a <button> nested inside the outer row <button>, which is invalid HTML; browsers reparent the inner element outside the outer one, breaking both the drag handle position and stopPropagation, so every drag-handle click also fires onSelect.
  • P2 — RepositorySection.tsx: The old isEditing guard on the reset useEffect was removed; any background update to currentRepoCloneUrl while the user is typing will silently wipe their in-progress input.
  • P2 — V2SessionsSection.tsx: The sessions <tbody> lost its divide-y divide-border/60 class when the border card was removed, leaving table rows with no visual separator at rest.

Confidence Score: 3/5

Mostly safe style refactor, but the nested-button HTML violation in the new agent sidebar will cause broken drag + double-fire click behaviour and should be fixed before merging.

One clear P1 (nested button HTML violation producing broken UI), plus two P2 issues. The P1 is isolated to the new V2 agent sidebar but directly breaks the intended drag-to-reorder UX for agents.

AgentsSettingsSidebar.tsx (P1 nested button), RepositorySection.tsx (P2 useEffect guard), V2SessionsSection.tsx (P2 missing row dividers)

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx New file: sidebar + drag-reorder for V2 agents. Contains a P1 HTML violation — <button> nested inside <button> for the drag handle, which browsers reparent, breaking both drag and click-to-select.
apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx Refactored to save-on-blur; removed the isEditing guard in the useEffect, meaning a background prop update can silently wipe mid-flight user input.
apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx Daemon section redesigned: Update/Force-restart buttons hoisted to header, chevron-based session disclosure replaces Show/Hide button. Missing divide-y on new bare <tbody> leaves session rows without visual separators.
apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx New file: detail panel for a selected V2 agent with save-on-blur for label, command, and prompt-args fields, inline transport toggle, and delete action. Logic looks correct.
apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsListSidebar/SettingsListSidebar.tsx New generic sidebar component with filter input, multi-group support, and a toolbar slot. Clean generic design with hideFilterWhenEmpty guard.
apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx Flattened Table to divide-y list; removed amber warning box in favour of a concise copy sentence; key-visibility warning still communicated via dialog description. Clean refactor.
apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/BillingOverview.tsx Hero cards replaced with borderless divide-y rows under a Plan section heading; layout and spacing standardised. Straightforward style refactor.
apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx Card-based layout replaced with flat IntegrationRow components. comingSoon prop and its guard removed cleanly. No behavioral changes.
apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx New component: save-on-blur project rename input with Escape-to-revert. Logic is correct; Enter triggers blur to commit, Escape restores the previous name.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[V2AgentsSettings] --> B[AgentsSettingsSidebar]
    A --> C[AgentDetail]
    B --> D[SettingsListSidebar generic]
    B --> E[AgentSidebarRow - DnD sortable]
    E -->|"⚠️ nested button"| F[outer button - onSelect]
    F --> G[inner button - drag handle]
    C --> H[save-on-blur: label / command / promptArgs]
    C --> I[inline transport toggle: argv / stdin]
    C --> J[Delete agent - destructive row]
    K[V2ProjectSettings] --> L[NameSection - save-on-blur rename NEW]
    K --> M[RepositorySection - save-on-blur]
    M -->|"⚠️ no isEditing guard"| N[useEffect resets on any prop change]
    M --> O[parseGitHubRemote - Open button]
    P[V2SessionsSection] --> Q[chevron disclosure replaces Show/Hide]
    P --> R[Update/ForceRestart hoisted to section header]
    P -->|"⚠️ missing divide-y"| S[bare tbody - no row separators]
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx:198-222
**Nested `<button>` inside `<button>` — invalid HTML**

The drag-handle trigger is rendered as a `<button>` child of the outer `<button>` (select row). The HTML spec forbids interactive content (including buttons) inside `<button>` elements. Browsers auto-correct this by hoisting the inner button outside the outer one, so the grip icon ends up mis-positioned in the DOM and `e.stopPropagation()` no longer intercepts the parent click, causing every drag-handle click to also fire `onSelect`.

Replace the inner `<button>` with a `<span>` (styled to look interactive) and apply the drag listeners to it — `@dnd-kit` supports any element as the activator node, not just buttons.

### Issue 2 of 3
apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx:22-24
**`useEffect` resets in-progress input on any prop change**

The old code guarded `setValue` with `if (!isEditing)`. The new `useEffect` fires unconditionally on every `currentRepoCloneUrl` change (including optimistic updates triggered by other settings panels or a background sync). If `currentRepoCloneUrl` updates while the user is actively typing a URL, their partially-entered text will be silently wiped.

Or alternatively, keep an `isEditing` ref set on focus/blur and gate the reset behind it, matching the prior behaviour.

```suggestion
	const isFocusedRef = useRef(false);
	useEffect(() => {
		if (!isFocusedRef.current) {
			setValue(currentRepoCloneUrl ?? "");
		}
	}, [currentRepoCloneUrl]);
```

### Issue 3 of 3
apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx:268
**Session rows lose all visual separation**

The old `<tbody>` had `className="divide-y divide-border/60"`, giving each session row a bottom border. The border container card was also removed. The new plain `<tbody>` has neither, so all session rows will run together — the `hover:bg-muted/30` row highlight is the only visual cue, but that only appears on hover, leaving the table unreadable at rest.

```suggestion
							<tbody className="divide-y divide-border/40">
```

Reviews (1): Last reviewed commit: "other styles" | Re-trigger Greptile

Comment on lines +198 to +222
type="button"
onClick={onSelect}
className={settingsListItemClass(isActive, "gap-2 w-full text-left")}
>
{icon ? (
<img
src={icon}
alt=""
className="size-4 object-contain shrink-0"
/>
) : null}
<span className="truncate flex-1">{row.label}</span>
</button>
<button
type="button"
ref={setActivatorNodeRef}
{...attributes}
{...listeners}
className={cn(
"absolute right-1 top-1/2 -translate-y-1/2 p-0.5 rounded text-muted-foreground/60 hover:text-foreground hover:bg-accent cursor-grab active:cursor-grabbing",
"opacity-0 group-hover/row:opacity-100 focus-visible:opacity-100 transition-opacity",
isDragging && "opacity-100",
)}
aria-label="Drag to reorder"
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nested <button> inside <button> — invalid HTML

The drag-handle trigger is rendered as a <button> child of the outer <button> (select row). The HTML spec forbids interactive content (including buttons) inside <button> elements. Browsers auto-correct this by hoisting the inner button outside the outer one, so the grip icon ends up mis-positioned in the DOM and e.stopPropagation() no longer intercepts the parent click, causing every drag-handle click to also fire onSelect.

Replace the inner <button> with a <span> (styled to look interactive) and apply the drag listeners to it — @dnd-kit supports any element as the activator node, not just buttons.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx
Line: 198-222

Comment:
**Nested `<button>` inside `<button>` — invalid HTML**

The drag-handle trigger is rendered as a `<button>` child of the outer `<button>` (select row). The HTML spec forbids interactive content (including buttons) inside `<button>` elements. Browsers auto-correct this by hoisting the inner button outside the outer one, so the grip icon ends up mis-positioned in the DOM and `e.stopPropagation()` no longer intercepts the parent click, causing every drag-handle click to also fire `onSelect`.

Replace the inner `<button>` with a `<span>` (styled to look interactive) and apply the drag listeners to it — `@dnd-kit` supports any element as the activator node, not just buttons.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 22 to +24
useEffect(() => {
if (!isEditing) setValue(currentRepoCloneUrl ?? "");
}, [currentRepoCloneUrl, isEditing]);

const startEdit = () => {
setIsEditing(true);
setTimeout(() => inputRef.current?.focus(), 0);
};

const cancelEdit = () => {
setValue(currentRepoCloneUrl ?? "");
setIsEditing(false);
};
}, [currentRepoCloneUrl]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 useEffect resets in-progress input on any prop change

The old code guarded setValue with if (!isEditing). The new useEffect fires unconditionally on every currentRepoCloneUrl change (including optimistic updates triggered by other settings panels or a background sync). If currentRepoCloneUrl updates while the user is actively typing a URL, their partially-entered text will be silently wiped.

Or alternatively, keep an isEditing ref set on focus/blur and gate the reset behind it, matching the prior behaviour.

Suggested change
useEffect(() => {
if (!isEditing) setValue(currentRepoCloneUrl ?? "");
}, [currentRepoCloneUrl, isEditing]);
const startEdit = () => {
setIsEditing(true);
setTimeout(() => inputRef.current?.focus(), 0);
};
const cancelEdit = () => {
setValue(currentRepoCloneUrl ?? "");
setIsEditing(false);
};
}, [currentRepoCloneUrl]);
const isFocusedRef = useRef(false);
useEffect(() => {
if (!isFocusedRef.current) {
setValue(currentRepoCloneUrl ?? "");
}
}, [currentRepoCloneUrl]);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx
Line: 22-24

Comment:
**`useEffect` resets in-progress input on any prop change**

The old code guarded `setValue` with `if (!isEditing)`. The new `useEffect` fires unconditionally on every `currentRepoCloneUrl` change (including optimistic updates triggered by other settings panels or a background sync). If `currentRepoCloneUrl` updates while the user is actively typing a URL, their partially-entered text will be silently wiped.

Or alternatively, keep an `isEditing` ref set on focus/blur and gate the reset behind it, matching the prior behaviour.

```suggestion
	const isFocusedRef = useRef(false);
	useEffect(() => {
		if (!isFocusedRef.current) {
			setValue(currentRepoCloneUrl ?? "");
		}
	}, [currentRepoCloneUrl]);
```

How can I resolve this? If you propose a fix, please make it concise.

<th className="px-2 py-2 text-left font-medium">Status</th>
</tr>
</thead>
<tbody>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Session rows lose all visual separation

The old <tbody> had className="divide-y divide-border/60", giving each session row a bottom border. The border container card was also removed. The new plain <tbody> has neither, so all session rows will run together — the hover:bg-muted/30 row highlight is the only visual cue, but that only appears on hover, leaving the table unreadable at rest.

Suggested change
<tbody>
<tbody className="divide-y divide-border/40">
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx
Line: 268

Comment:
**Session rows lose all visual separation**

The old `<tbody>` had `className="divide-y divide-border/60"`, giving each session row a bottom border. The border container card was also removed. The new plain `<tbody>` has neither, so all session rows will run together — the `hover:bg-muted/30` row highlight is the only visual cue, but that only appears on hover, leaving the table unreadable at rest.

```suggestion
							<tbody className="divide-y divide-border/40">
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 issues found across 68 files

You’re at about 97% of the monthly review limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx:198">
P2: Avoid nesting a drag-handle `<button>` inside the row `<button>`; nested interactive elements are invalid HTML and can break focus/click behavior.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx:229">
P2: The session status dot is always green in the expandable state, even when there are zero alive sessions.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx:24">
P3: When trimmed input equals the current name, reset the field value so the UI stays in sync with persisted data.</violation>

<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx:38">
P2: Escape can still trigger a rename because blur commits before the state reset is guaranteed to be applied.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx:192">
P2: The revoke button is hover-only (`opacity-0 group-hover:opacity-100`), making it effectively invisible for keyboard/no-hover users. Add focus-based visibility so the action is discoverable and usable without mouse hover.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx:50">
P2: Pressing Escape can still save the edited repository URL because blur is triggered before the state reset is applied.</violation>

<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx:50">
P2: Guard this state sync while the repository field is focused; otherwise external updates to `currentRepoCloneUrl` can overwrite in-progress user edits.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx:54">
P1: The new v2-flag conditional hides all v1 projects in the sidebar, causing an empty/incorrect project list for users who still have v1 projects.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx:130">
P2: The auto-select effect can overwrite the newly added agent selection with the first existing agent before refetch completes.</violation>

<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx:180">
P3: The detail panel shows the empty-state message while data is still loading, which misrepresents loading as “no agents”.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/ModelsSettings.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/ModelsSettings.tsx:335">
P2: Clicking Reset from a focused advanced field can trigger two competing saves (blur-save + reset-save), which can persist stale Anthropic override values due to request race timing.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/PresetEditorDialog.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/PresetEditorDialog.tsx:204">
P2: Wrap the async directory-picker mutation in `try/catch` so failures don’t become unhandled promise rejections.

(Based on your team's feedback about handling async rejections explicitly.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

/>
<span
aria-hidden
className="size-1.5 rounded-full bg-emerald-500"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The session status dot is always green in the expandable state, even when there are zero alive sessions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx, line 229:

<comment>The session status dot is always green in the expandable state, even when there are zero alive sessions.</comment>

<file context>
@@ -166,99 +167,105 @@ function V2SessionsSectionInner() {
+							/>
+							<span
+								aria-hidden
+								className="size-1.5 rounded-full bg-emerald-500"
+							/>
+							{sessionCountLabel}
</file context>
Suggested change
className="size-1.5 rounded-full bg-emerald-500"
className={cn(
"size-1.5 rounded-full",
aliveCount && aliveCount > 0 ? "bg-emerald-500" : "bg-muted-foreground/60",
)}

e.preventDefault();
(e.target as HTMLInputElement).blur();
}
if (e.key === "Escape") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Escape can still trigger a rename because blur commits before the state reset is guaranteed to be applied.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx, line 38:

<comment>Escape can still trigger a rename because blur commits before the state reset is guaranteed to be applied.</comment>

<file context>
@@ -0,0 +1,47 @@
+					e.preventDefault();
+					(e.target as HTMLInputElement).blur();
+				}
+				if (e.key === "Escape") {
+					e.preventDefault();
+					setValue(currentName);
</file context>

<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The revoke button is hover-only (opacity-0 group-hover:opacity-100), making it effectively invisible for keyboard/no-hover users. Add focus-based visibility so the action is discoverable and usable without mouse hover.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx, line 192:

<comment>The revoke button is hover-only (`opacity-0 group-hover:opacity-100`), making it effectively invisible for keyboard/no-hover users. Add focus-based visibility so the action is discoverable and usable without mouse hover.</comment>

<file context>
@@ -138,131 +130,100 @@ export function ApiKeysSettings({ visibleItems }: ApiKeysSettingsProps) {
+									<Button
+										variant="ghost"
+										size="icon"
+										className="h-8 w-8 text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
+										onClick={() => handleRevokeKey(key.id, key.name)}
+										aria-label="Revoke key"
</file context>
Suggested change
className="h-8 w-8 text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
className="h-8 w-8 text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 transition-opacity"

{advancedOpen && hasAdvancedContent ? (
<button
type="button"
onClick={resetAnthropicAdvanced}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Clicking Reset from a focused advanced field can trigger two competing saves (blur-save + reset-save), which can persist stale Anthropic override values due to request race timing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/ModelsSettings.tsx, line 335:

<comment>Clicking Reset from a focused advanced field can trigger two competing saves (blur-save + reset-save), which can persist stale Anthropic override values due to request race timing.</comment>

<file context>
@@ -247,51 +267,154 @@ export function ModelsSettings({ visibleItems }: ModelsSettingsProps) {
+									{advancedOpen && hasAdvancedContent ? (
+										<button
+											type="button"
+											onClick={resetAnthropicAdvanced}
+											disabled={isSavingAnthropicConfig}
+											className="text-xs text-muted-foreground hover:text-destructive transition-colors disabled:opacity-50"
</file context>
Suggested change
onClick={resetAnthropicAdvanced}
onMouseDown={(event) => {
event.preventDefault();
}}
onClick={resetAnthropicAdvanced}

Comment on lines +204 to +215
const result = await selectDirectory.mutateAsync({
title: "Select preset directory",
defaultPath: browseDefaultPath,
});
if (!result.canceled && result.path) {
if (originWorkspace?.worktreePath) {
onDirectorySelect(
toPresetDirectoryValue(originWorkspace.worktreePath, result.path),
);
return;
}
onDirectorySelect(result.path);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Wrap the async directory-picker mutation in try/catch so failures don’t become unhandled promise rejections.

(Based on your team's feedback about handling async rejections explicitly.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/PresetEditorDialog.tsx, line 204:

<comment>Wrap the async directory-picker mutation in `try/catch` so failures don’t become unhandled promise rejections.

(Based on your team's feedback about handling async rejections explicitly.) </comment>

<file context>
@@ -0,0 +1,444 @@
+		);
+
+	const handleBrowseDirectory = async () => {
+		const result = await selectDirectory.mutateAsync({
+			title: "Select preset directory",
+			defaultPath: browseDefaultPath,
</file context>
Suggested change
const result = await selectDirectory.mutateAsync({
title: "Select preset directory",
defaultPath: browseDefaultPath,
});
if (!result.canceled && result.path) {
if (originWorkspace?.worktreePath) {
onDirectorySelect(
toPresetDirectoryValue(originWorkspace.worktreePath, result.path),
);
return;
}
onDirectorySelect(result.path);
try {
const result = await selectDirectory.mutateAsync({
title: "Select preset directory",
defaultPath: browseDefaultPath,
});
if (!result.canceled && result.path) {
if (originWorkspace?.worktreePath) {
onDirectorySelect(
toPresetDirectoryValue(originWorkspace.worktreePath, result.path),
);
return;
}
onDirectorySelect(result.path);
}
} catch (error) {
console.warn("Failed to select preset directory", error);
}

setValue(currentName);
return;
}
if (trimmed === currentName) return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: When trimmed input equals the current name, reset the field value so the UI stays in sync with persisted data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx, line 24:

<comment>When trimmed input equals the current name, reset the field value so the UI stays in sync with persisted data.</comment>

<file context>
@@ -0,0 +1,47 @@
+			setValue(currentName);
+			return;
+		}
+		if (trimmed === currentName) return;
+		projectActions.renameProject(projectId, trimmed);
+	};
</file context>

}}
/>
) : (
<EmptyState />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The detail panel shows the empty-state message while data is still loading, which misrepresents loading as “no agents”.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx, line 180:

<comment>The detail panel shows the empty-state message while data is still loading, which misrepresents loading as “no agents”.</comment>

<file context>
@@ -124,140 +101,113 @@ export function V2AgentsSettings() {
+						}}
+					/>
+				) : (
+					<EmptyState />
+				)}
+			</div>
</file context>
Suggested change
<EmptyState />
configsQuery.isLoading ? <div className="p-6 text-sm text-muted-foreground">Loading agent settings...</div> : <EmptyState />

Kitenite added 2 commits May 4, 2026 01:27
Resolve conflict in LinkTierMapper.tsx by adopting origin/main's
clickPolicy types (Surface prop, modifierLabel/actionLabel helpers,
shift tier) while keeping the v1 borderless layout and size="sm"
select trigger.

Address Greptile review:
- AgentsSettingsSidebar: replace nested <button> drag handle with a
  <span role="button"> so click events stop on the inner element
- RepositorySection: gate the prop-sync useEffect on a focus ref so
  background updates don't wipe in-progress user input
- V2SessionsSection: restore divide-y on the daemon sessions tbody
Did not work in practice. Restore the simple Account redirect.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/components/RingtonesSettings/RingtonesSettings.tsx (1)

221-236: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing return after the catch block leaves a dangling timer on preview failure.

When ringtone.preview.mutate rejects, setPlayingId(null) is called correctly, but execution falls through to the setTimeout setup on line 233. This plants a phantom timer (up to ~5.5 s) that writes to previewTimerRef.current, which can mask a subsequent play attempt's timer or confuse the cleanup path — even though the timer callback itself ends up being a no-op.

🐛 Proposed fix
 		} catch (error) {
 			console.error("Failed to play ringtone:", error);
 			setPlayingId(null);
+			return;
 		}
 
 		// Auto-reset after the ringtone's actual duration (with 500ms buffer)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/components/RingtonesSettings/RingtonesSettings.tsx`
around lines 221 - 236, The catch in the ringtone preview flow currently logs
the error and calls setPlayingId(null) but does not return, so execution
continues and schedules a preview timer; modify the preview logic (around
electronTrpcClient.ringtone.preview.mutate, the catch block, and the subsequent
durationMs/setTimeout) to return early from the function after handling the
error (i.e., after console.error and setPlayingId(null)) so that
previewTimerRef.current is not set on preview failure; ensure previewTimerRef is
only assigned when the preview mutate succeeds and the timer cleanup behavior
remains unchanged.
apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx (1)

184-228: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Programmatically label the branch-prefix controls.

This row still only has a visual label. A screen reader will announce the select by its current value, and the custom text field falls back to its placeholder. Please wire an accessible name onto both controls.

♿ Suggested fix
-							<Label className="text-sm font-medium">Branch prefix</Label>
+							<Label htmlFor="branch-prefix-mode" className="text-sm font-medium">
+								Branch prefix
+							</Label>
...
-								<SelectTrigger className="w-[180px]">
+								<SelectTrigger
+									id="branch-prefix-mode"
+									className="w-[180px]"
+									aria-label="Branch prefix mode"
+								>
...
 								<Input
 									placeholder="Prefix"
+									aria-label="Custom branch prefix"
 									value={customPrefixInput}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx`
around lines 184 - 228, The branch-prefix controls lack accessible names; add
one by giving the existing Label element a stable id (e.g., branchPrefixLabel)
and wire that id into the form controls so screen readers announce the visual
label: set aria-labelledby={labelId} on the Select (or its trigger) and on the
Input used for customPrefixInput; ensure the same id is used for both when
visible, and if you prefer a standalone name instead, set descriptive aria-label
values on Select and Input (e.g., "Branch prefix mode" and "Custom branch
prefix") and keep controls' disabled/pending logic unchanged (targets: Select,
SelectTrigger/SelectValue, and Input and handlers handleBranchPrefixModeChange
and handleCustomPrefixBlur).
apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx (3)

229-233: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cancelling the "Generate API key" dialog leaves newKeyName stale.

onOpenChange only calls setShowGenerateDialog — it does not reset newKeyName. If a user types a name, cancels, then reopens the dialog, the previous name is pre-filled.

🛠️ Proposed fix
-		<Button
-			variant="outline"
-			onClick={() => setShowGenerateDialog(false)}
-		>
+		<Button
+			variant="outline"
+			onClick={() => {
+				setShowGenerateDialog(false);
+				setNewKeyName("");
+			}}
+		>

Or alternatively, reset inside the onOpenChange handler on the Dialog:

-<Dialog open={showGenerateDialog} onOpenChange={setShowGenerateDialog}>
+<Dialog open={showGenerateDialog} onOpenChange={(open) => {
+	setShowGenerateDialog(open);
+	if (!open) setNewKeyName("");
+}}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx`
around lines 229 - 233, The cancel flow for the "Generate API key" dialog
doesn't clear the newKeyName state, so previously typed values persist; update
the dialog close logic to reset newKeyName whenever the dialog is closed: call
setNewKeyName("") alongside setShowGenerateDialog(false) (e.g., in the Button
onClick handler that currently calls setShowGenerateDialog(false) and/or in the
Dialog onOpenChange handler) so that newKeyName is cleared when the dialog is
cancelled or closed.

60-79: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

handleGenerateKey swallows errors silently — add user-facing feedback.

The catch block only logs to the console. When apiTrpcClient.apiKey.create.mutate fails, the dialog remains open with no indication to the user about what went wrong.

🛠️ Proposed fix
 	} catch (error) {
 		console.error("[api-keys] Failed to generate API key:", error);
+		toast.error("Failed to generate API key. Please try again.");
 	} finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx`
around lines 60 - 79, handleGenerateKey currently only logs failures to console
so the user sees no feedback and the dialog stays open; update it to surface
errors to the UI by catching the error and setting a user-visible error state
(e.g., setGenerateError / setApiKeyGenerateError) or invoking the app's
toast/notification utility inside the catch block, ensure setIsGenerating(false)
still runs in finally, and keep or update dialog visibility accordingly (keep
showGenerateDialog open so the user can correct input and display the error text
in the ApiKeysSettings generate dialog). Reference: function handleGenerateKey
and state setters setIsGenerating, setShowGenerateDialog, setShowNewKeyDialog,
setNewKeyValue, setNewKeyName when making the change.

81-97: ⚠️ Potential issue | 🟠 Major

Add error handling to the revoke handler to prevent silent failures.

The onClick handler at lines 90–92 lacks a try-catch block. If authClient.apiKey.delete rejects, the error is silently swallowed and toast.success is never called, leaving the user unaware that revocation failed. This contradicts the pattern used in handleGenerateKey (lines 74–76), which properly catches errors and notifies the user.

🛠️ Proposed fix
 			onClick: async () => {
-				await authClient.apiKey.delete({ keyId: id });
-				toast.success("API key revoked");
+				try {
+					await authClient.apiKey.delete({ keyId: id });
+					toast.success("API key revoked");
+				} catch (error) {
+					console.error("[api-keys] Failed to revoke API key:", error);
+					toast.error("Failed to revoke API key. Please try again.");
+				}
 			},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx`
around lines 81 - 97, The revoke handler handleRevokeKey currently calls
authClient.apiKey.delete inside the alert action without error handling; wrap
the async onClick body in a try/catch so that if authClient.apiKey.delete({
keyId: id }) rejects you show toast.error with the error message (and avoid
showing toast.success), and only call toast.success when the delete completes
successfully; update the onClick in the alert actions to use this try/catch
pattern (referencing handleRevokeKey and authClient.apiKey.delete) so failures
are surfaced to the user.
🧹 Nitpick comments (6)
apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/components/SettingsSection/SettingsSection.tsx (1)

27-29: ⚡ Quick win

text-xs here diverges from the text-sm description size used in the shared SettingsSection (ProjectSettings.tsx line 74).

The PR's goal is a unified visual vocabulary, and the shared helper's description was bumped to text-sm. This component went in the opposite direction (text-smtext-xs), which will produce visibly smaller description text on the Models page compared to all other settings pages. If the discrepancy is intentional for a denser layout, a brief comment would help; otherwise this should align with the rest.

🎨 Proposed fix to align with the shared vocabulary
-					<p className="text-xs text-muted-foreground mt-0.5">
+					<p className="text-sm text-muted-foreground mt-0.5">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/components/SettingsSection/SettingsSection.tsx`
around lines 27 - 29, In SettingsSection (component SettingsSection.tsx) the
description paragraph uses the class "text-xs" which diverges from the shared
SettingsSection/ProjectSettings description size ("text-sm"); update the class
on the <p> that renders {description} from "text-xs" to "text-sm" to match the
shared visual vocabulary (or, if the smaller size was intentional, add a brief
inline comment above the <p> explaining why this component deviates). Ensure you
modify the class string on the paragraph that contains {description} in
SettingsSection so it matches the other settings pages.
apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx (1)

129-163: 💤 Low value

Consider divide-y over space-y-1 for visual row separation

The PR description explicitly calls out "flat divide-y lists" for the integrations page, but the container uses space-y-1 (a 4px gap) with no border separator between rows. The py-3 padding on each IntegrationRow means rows sit very close together visually without a divider.

♻️ Proposed change
-      <div className="space-y-1">
+      <div className="divide-y">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx`
around lines 129 - 163, Replace the vertical spacing utility on the integrations
container to use a flat divider instead of gaps: in the parent div that
currently has className="space-y-1", remove "space-y-1" and add "divide-y
divide-neutral-200" (or your design-system's divider color) so IntegrationRow
entries are visually separated by a horizontal rule while preserving each row's
existing padding (e.g., py-3) and props (IntegrationRow, isConnected,
connectedOrgName, isLoading, onManage).
apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx (1)

177-195: ⚡ Quick win

SettingRow is nearly identical to SettingsRow in OrganizationSettings.tsx — extract to a shared component.

Both files define a local row helper with the same flex layout, flex-1 min-w-0 label column, optional hint, and shrink-0 children slot. The only meaningful differences are py-2.5 on the wrapper and the use of <Label htmlFor> (in OrganizationSettings). Given that this PR's goal is to standardise the settings vocabulary across every page, shipping two private copies with different names (SettingRow / SettingsRow) and slightly diverging internals works against that goal.

A single shared component (e.g., SettingsSection/SettingRow) would give all settings pages a consistent row primitive and prevent further drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx`
around lines 177 - 195, SettingRow in AccountSettings (function SettingRow) is a
duplicate of SettingsRow used in OrganizationSettings.tsx; extract a single
shared SettingsRow/SettingRow component in a common UI folder and replace both
local definitions with an import. Move the shared JSX (wrapper div with flex
layout, inner label block using "flex-1 min-w-0", optional hint rendering, and
children slot in "flex-shrink-0") into the new component (preserve props: label,
hint?, children) and update callers to use the shared component (adjust any
wrapper spacing like py-2.5 via a prop or wrapper class at call sites if needed)
so both AccountSettings and OrganizationSettings import the same component
instead of defining SettingRow/SettingsRow locally.
apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/BillingDetails/BillingDetails.tsx (1)

150-168: ⚡ Quick win

DetailRow is reimplemented inline in CurrentPlanCard and UpgradeCard — extract to a shared component.

All three billing components implement the same row skeleton:

flex items-center justify-between gap-8 py-3
  └─ min-w-0 flex-1  (label + hint)
  └─ shrink-0        (action)

Because DetailRow is unexported, CurrentPlanCard.tsx and UpgradeCard.tsx duplicate the layout inline. Exporting DetailRow (or promoting it to a shared BillingRow/SettingRow component) would let all three billing rows share the same layout contract and be updated in one place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/BillingDetails/BillingDetails.tsx`
around lines 150 - 168, The DetailRow layout is duplicated in CurrentPlanCard
and UpgradeCard; extract and export the existing DetailRow (and its
DetailRowProps) as a shared component (e.g., BillingRow or keep the name
DetailRow) and replace the inline implementations in CurrentPlanCard and
UpgradeCard with imports of that shared component so all three billing
components (BillingDetails/DetailRow, CurrentPlanCard, UpgradeCard) use the same
exported component and prop contract.
apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx (1)

199-229: ⚡ Quick win

Prompt transport toggle is missing ARIA group semantics.

The segmented argv / stdin control has exclusive-selection semantics (a radio group), but screen readers see two plain buttons with no stated relationship or selection state.

♿ Proposed fix
-<div className="inline-flex rounded-md border border-border overflow-hidden">
+<div
+    role="radiogroup"
+    aria-label="Prompt transport"
+    className="inline-flex rounded-md border border-border overflow-hidden"
+>
     <button
         type="button"
         onClick={() => handleTransportChange("argv")}
+        role="radio"
+        aria-checked={promptTransport === "argv"}
         className={cn(
             "px-3 py-1 text-xs font-medium transition-colors",
             promptTransport === "argv"
                 ? "bg-accent text-accent-foreground"
                 : "bg-transparent text-muted-foreground hover:bg-accent/50",
         )}
     >
         argv
     </button>
     <button
         type="button"
         onClick={() => handleTransportChange("stdin")}
+        role="radio"
+        aria-checked={promptTransport === "stdin"}
         className={cn(
             "px-3 py-1 text-xs font-medium transition-colors border-l border-border",
             promptTransport === "stdin"
                 ? "bg-accent text-accent-foreground"
                 : "bg-transparent text-muted-foreground hover:bg-accent/50",
         )}
     >
         stdin
     </button>
 </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx`
around lines 199 - 229, Wrap the two buttons in a radiogroup and give each
button radio semantics so assistive tech sees them as an exclusive-selection
control: add role="radiogroup" to the div and for each option set role="radio",
aria-checked={promptTransport === "argv" || "stdin"}, and
tabIndex={promptTransport === value ? 0 : -1}; keep the existing onClick
pointing to handleTransportChange and add an onKeyDown handler on each radio to
handle ArrowLeft/ArrowRight (calling handleTransportChange and moving focus) so
keyboard arrow navigation works; preserve the current cn classes and visual
styling while adding these ARIA attributes on the StackedField children.
apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx (1)

11-15: ⚡ Quick win

Use the renderer alias for this shared sidebar import.

This new relative path is brittle and goes against the desktop import convention already used in the same file.

♻️ Suggested change
 import {
 	type SettingsListGroup,
 	settingsListItemClass,
 	SettingsListSidebar,
-} from "../../../components/SettingsListSidebar";
+} from "renderer/routes/_authenticated/settings/components/SettingsListSidebar";

As per coding guidelines, apps/desktop/**/*.{ts,tsx}: Use alias as defined in tsconfig.json when possible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx`
around lines 11 - 15, The import for SettingsListSidebar, settingsListItemClass,
and SettingsListGroup uses a brittle relative path; update the import to use the
renderer alias defined in tsconfig (e.g. import { type SettingsListGroup,
settingsListItemClass, SettingsListSidebar } from "renderer/…") so the file
ProjectsSettingsSidebar.tsx follows the apps/desktop alias convention and avoids
fragile relative paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx`:
- Around line 143-149: The Email input lacks a programmatic label association;
update SettingRow to render a proper <Label htmlFor={htmlFor}> (mirroring
OrganizationSettings' SettingsRow) and ensure each Input in AccountSettings
(e.g., the Email and Name inputs inside AccountSettings.tsx) receives a matching
id prop (unique per field) and the SettingRow call sites pass that same htmlFor
value; also remove reliance on placeholder for labeling and prefer the explicit
label/id pairing so assistive tech can announce the controls.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/components/ProfileSkeleton/ProfileSkeleton.tsx`:
- Around line 11-14: ProfileSkeleton renders a phantom rectangular placeholder
that doesn't exist in the real Avatar SettingRow; in the ProfileSkeleton
component remove the extra Skeleton with className "h-8 w-20" (the rectangular
placeholder) so only the circular Skeleton remains and the markup matches
AccountSettings' Avatar row; reference the ProfileSkeleton component and ensure
the remaining Skeleton keeps its "size-12 rounded-full" classes and alignment
consistent with SettingRow in AccountSettings.tsx.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx`:
- Around line 97-101: handleLabelBlur currently checks trimmed length but calls
updateMutation.mutate with the raw label, which can persist leading/trailing
whitespace; change handleLabelBlur to pass the trimmed string to
updateMutation.mutate (e.g., compute const trimmed = label.trim() and call
updateMutation.mutate({ label: trimmed })) and keep the existing comparison
using config.label so you only update when the effective label changed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx`:
- Around line 216-220: The drag-handle is hidden by default (opacity-0) which
breaks touch users; update AgentsSettingsSidebar to detect touch devices (e.g.,
add an isTouchDevice state set in a useEffect using window.matchMedia('(hover:
none)') or 'ontouchstart' probe) and include that flag in the handle className
so the handle gets "opacity-100" when isTouchDevice is true (in addition to the
existing isDragging condition), e.g., add isTouchDevice && "opacity-100"
alongside isDragging to ensure the handle is visible on touch devices.
- Around line 68-74: The KeyboardSensor used in useSensors is not wired to
sortableKeyboardCoordinates, causing keyboard navigation to use default 25px
increments instead of moving between sortable items; update the
useSensor(KeyboardSensor) call inside useSensors to pass
sortableKeyboardCoordinates as the coordinate getter (i.e.,
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })) so
KeyboardSensor uses the sortable list coordinates; ensure you import
sortableKeyboardCoordinates where AgentsSettingsSidebar defines useSensors and
keep the existing MouseSensor and TouchSensor options intact.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx`:
- Around line 124-131: The effect in V2AgentsSettings is rerunning when
selectedAgentId changes and thus overwrites the new “select added agent” and
“clear selection on reset” flows; restrict the effect to run only when configs
change (remove selectedAgentId from the dependency array) and keep the internal
checks as-is so selection is only updated on config list changes (useEffect(...,
[configs]) with the same body that sets selectedAgentId to null if
configs.length === 0 or to configs[0].id only when the current selectedAgentId
is not present).

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx`:
- Around line 189-197: The revoke Button in ApiKeysSettings is hidden on
keyboard focus because it uses "opacity-0 group-hover:opacity-100" only; update
the Button's className to also include "focus-visible:opacity-100" (and
optionally "focus:opacity-100") so keyboard-focused users see the control, and
ensure the existing aria-label remains; locate the Button element in
ApiKeysSettings (the one calling handleRevokeKey) and add those focus classes to
its className.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx`:
- Around line 87-121: The SelectTrigger in ThemeRow lacks an accessible name for
screen readers; update the SelectTrigger (used inside ThemeRow) to expose the
prop label as its accessible name—e.g., add an aria-label or aria-labelledby
that references the label text so the trigger announces whether it controls
"Theme", "Light theme", or "Dark theme" (use the existing label variable passed
into ThemeRow). Ensure the accessible name is applied to the SelectTrigger
element that wraps SelectValue so both system and explicit theme displays are
properly announced.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/UpgradeCard/UpgradeCard.tsx`:
- Around line 11-23: The displayed monthly price uses a raw division (monthly =
plan.price?.monthly ? plan.price.monthly / 100 : 0) which can produce truncated
decimals; update UpgradeCard to format the amount with Intl.NumberFormat like
the existing formatAmount helper (or call formatAmount if exported) instead of
interpolating ${monthly} directly so prices render as proper currency (e.g., use
plan.price.monthly through formatAmount or an equivalent Intl.NumberFormat call
where monthly is currently used in the JSX).

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/ModelsSettings.tsx`:
- Around line 152-167: The blur-save/reset race happens because
handleAnthropicFormBlur saves the old form when an input blur fires before
reset; fix by adding a short-lived guard flag (a mutable ref like
isProgrammaticChangeRef) that resetAnthropicAdvanced sets to true before calling
setAnthropicForm and saveAnthropicForm and clears after the reset save
completes, and update handleAnthropicFormBlur to return early if that flag is
set; do the same pattern for the analogous handlers at lines 332-340 (the other
model provider reset/blur handlers) so programmatic resets don't trigger the
blur save.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/organization/components/OrganizationSettings/OrganizationSettings.tsx`:
- Around line 259-270: The logo button in OrganizationSettings is missing the
upload-in-progress guard, so update the button that calls handleLogoUpload
(rendering OrganizationLogo) to also disable while selectImageMutation.isPending
— e.g., change the disabled prop to combine the ownership check and the mutation
pending flag (disabled={!isOwner || selectImageMutation.isPending}) so it
matches the avatar/upload pattern in AccountSettings.tsx and prevents
re-entry/double toasts during the async upload flow.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/PresetEditorDialog.tsx`:
- Around line 203-217: The handleBrowseDirectory function calls
selectDirectory.mutateAsync directly and can throw, so wrap the await call in a
try/catch inside handleBrowseDirectory (the function defined near
PresetEditorDialog) to prevent unhandled promise rejections; on error,
log/report the error (use existing logger or set an error state/notification)
and ensure any UI state that disables the browse button is reset so the button
is re-enabled, while keeping the successful-path behavior that calls
onDirectorySelect (including using toPresetDirectoryValue when
originWorkspace?.worktreePath exists).

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx`:
- Around line 216-232: The expand/collapse button in V2SessionsSection lacks an
aria-expanded attribute; update the button (the element that calls
setShowSessionList and uses showSessionList) to include
aria-expanded={showSessionList} so assistive tech can read the current state,
and optionally add aria-controls pointing to the session list's id (ensure the
session list element has a matching id) to link the button to the collapsible
region.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/`$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx:
- Around line 18-42: The Escape handler currently calls setValue(currentName)
(which is batched) then blur(), causing onBlur → commit() to read the stale
draft value and accidentally rename; fix by introducing a synchronous ref flag
(e.g., isDiscardRef) used by onKeyDown Escape to mark that the change was
discarded, and update commit() to check that ref and skip rename if set,
clearing the ref afterwards; reference the commit function, value/currentName
state, the onBlur handler, and the onKeyDown Escape branch to implement this
behavior.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/`$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx:
- Around line 48-52: The Escape handler currently does
setValue(currentRepoCloneUrl ?? "") then immediately triggers blur which invokes
onBlur → commit while the state update is still pending, causing commit to see
the old draft; modify the handler and related onBlur/commit logic to use an
"escapeRef" boolean flag (or similar) on keydown: when Escape is pressed, set
escapeRef.current = true, reset the input visually via
setValue(currentRepoCloneUrl ?? ""), call blur, and in onBlur/commit check
escapeRef.current—if true, clear the flag and skip calling
updateRepository/commit; ensure the flag is reset for normal blurs so other
paths still commit changes.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx`:
- Around line 229-233: The cancel flow for the "Generate API key" dialog doesn't
clear the newKeyName state, so previously typed values persist; update the
dialog close logic to reset newKeyName whenever the dialog is closed: call
setNewKeyName("") alongside setShowGenerateDialog(false) (e.g., in the Button
onClick handler that currently calls setShowGenerateDialog(false) and/or in the
Dialog onOpenChange handler) so that newKeyName is cleared when the dialog is
cancelled or closed.
- Around line 60-79: handleGenerateKey currently only logs failures to console
so the user sees no feedback and the dialog stays open; update it to surface
errors to the UI by catching the error and setting a user-visible error state
(e.g., setGenerateError / setApiKeyGenerateError) or invoking the app's
toast/notification utility inside the catch block, ensure setIsGenerating(false)
still runs in finally, and keep or update dialog visibility accordingly (keep
showGenerateDialog open so the user can correct input and display the error text
in the ApiKeysSettings generate dialog). Reference: function handleGenerateKey
and state setters setIsGenerating, setShowGenerateDialog, setShowNewKeyDialog,
setNewKeyValue, setNewKeyName when making the change.
- Around line 81-97: The revoke handler handleRevokeKey currently calls
authClient.apiKey.delete inside the alert action without error handling; wrap
the async onClick body in a try/catch so that if authClient.apiKey.delete({
keyId: id }) rejects you show toast.error with the error message (and avoid
showing toast.success), and only call toast.success when the delete completes
successfully; update the onClick in the alert actions to use this try/catch
pattern (referencing handleRevokeKey and authClient.apiKey.delete) so failures
are surfaced to the user.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx`:
- Around line 184-228: The branch-prefix controls lack accessible names; add one
by giving the existing Label element a stable id (e.g., branchPrefixLabel) and
wire that id into the form controls so screen readers announce the visual label:
set aria-labelledby={labelId} on the Select (or its trigger) and on the Input
used for customPrefixInput; ensure the same id is used for both when visible,
and if you prefer a standalone name instead, set descriptive aria-label values
on Select and Input (e.g., "Branch prefix mode" and "Custom branch prefix") and
keep controls' disabled/pending logic unchanged (targets: Select,
SelectTrigger/SelectValue, and Input and handlers handleBranchPrefixModeChange
and handleCustomPrefixBlur).

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/components/RingtonesSettings/RingtonesSettings.tsx`:
- Around line 221-236: The catch in the ringtone preview flow currently logs the
error and calls setPlayingId(null) but does not return, so execution continues
and schedules a preview timer; modify the preview logic (around
electronTrpcClient.ringtone.preview.mutate, the catch block, and the subsequent
durationMs/setTimeout) to return early from the function after handling the
error (i.e., after console.error and setPlayingId(null)) so that
previewTimerRef.current is not set on preview failure; ensure previewTimerRef is
only assigned when the preview mutate succeeds and the timer cleanup behavior
remains unchanged.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx`:
- Around line 177-195: SettingRow in AccountSettings (function SettingRow) is a
duplicate of SettingsRow used in OrganizationSettings.tsx; extract a single
shared SettingsRow/SettingRow component in a common UI folder and replace both
local definitions with an import. Move the shared JSX (wrapper div with flex
layout, inner label block using "flex-1 min-w-0", optional hint rendering, and
children slot in "flex-shrink-0") into the new component (preserve props: label,
hint?, children) and update callers to use the shared component (adjust any
wrapper spacing like py-2.5 via a prop or wrapper class at call sites if needed)
so both AccountSettings and OrganizationSettings import the same component
instead of defining SettingRow/SettingsRow locally.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx`:
- Around line 199-229: Wrap the two buttons in a radiogroup and give each button
radio semantics so assistive tech sees them as an exclusive-selection control:
add role="radiogroup" to the div and for each option set role="radio",
aria-checked={promptTransport === "argv" || "stdin"}, and
tabIndex={promptTransport === value ? 0 : -1}; keep the existing onClick
pointing to handleTransportChange and add an onKeyDown handler on each radio to
handle ArrowLeft/ArrowRight (calling handleTransportChange and moving focus) so
keyboard arrow navigation works; preserve the current cn classes and visual
styling while adding these ARIA attributes on the StackedField children.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/BillingDetails/BillingDetails.tsx`:
- Around line 150-168: The DetailRow layout is duplicated in CurrentPlanCard and
UpgradeCard; extract and export the existing DetailRow (and its DetailRowProps)
as a shared component (e.g., BillingRow or keep the name DetailRow) and replace
the inline implementations in CurrentPlanCard and UpgradeCard with imports of
that shared component so all three billing components (BillingDetails/DetailRow,
CurrentPlanCard, UpgradeCard) use the same exported component and prop contract.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx`:
- Around line 129-163: Replace the vertical spacing utility on the integrations
container to use a flat divider instead of gaps: in the parent div that
currently has className="space-y-1", remove "space-y-1" and add "divide-y
divide-neutral-200" (or your design-system's divider color) so IntegrationRow
entries are visually separated by a horizontal rule while preserving each row's
existing padding (e.g., py-3) and props (IntegrationRow, isConnected,
connectedOrgName, isLoading, onManage).

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/components/SettingsSection/SettingsSection.tsx`:
- Around line 27-29: In SettingsSection (component SettingsSection.tsx) the
description paragraph uses the class "text-xs" which diverges from the shared
SettingsSection/ProjectSettings description size ("text-sm"); update the class
on the <p> that renders {description} from "text-xs" to "text-sm" to match the
shared visual vocabulary (or, if the smaller size was intentional, add a brief
inline comment above the <p> explaining why this component deviates). Ensure you
modify the class string on the paragraph that contains {description} in
SettingsSection so it matches the other settings pages.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx`:
- Around line 11-15: The import for SettingsListSidebar, settingsListItemClass,
and SettingsListGroup uses a brittle relative path; update the import to use the
renderer alias defined in tsconfig (e.g. import { type SettingsListGroup,
settingsListItemClass, SettingsListSidebar } from "renderer/…") so the file
ProjectsSettingsSidebar.tsx follows the apps/desktop alias convention and avoids
fragile relative paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60d69476-28d1-4f5d-9c04-5acaf7f7d8f2

📥 Commits

Reviewing files that changed from the base of the PR and between c088df1 and ae3ef0e.

📒 Files selected for processing (72)
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/components/ProfileSkeleton/ProfileSkeleton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/AgentsSettings/AgentsSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/V2AgentsSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/V2AgentCard/V2AgentCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/V2AgentCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/FontSettingSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/MarkdownStyleSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/SystemThemeCard/SystemThemeCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/SystemThemeCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeCard/ThemeCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/BillingOverview.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/BillingDetails/BillingDetails.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/CurrentPlanCard/CurrentPlanCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/RecentInvoices/RecentInvoices.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/billing/components/BillingOverview/components/UpgradeCard/UpgradeCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsListSidebar/SettingsListSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsListSidebar/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/HostHeader/HostHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/components/HostsSettingsSidebar/HostsSettingsSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/members/components/MembersSettings/components/InviteMemberButton/InviteMemberButton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/ModelsSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/components/ConfigRow/ConfigRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/models/components/ModelsSettings/components/SettingsSection/SettingsSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/organization/components/OrganizationSettings/OrganizationSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/permissions/components/PermissionsSettings/PermissionsSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/cloud/secrets/components/SecretsSettings/SecretsSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/components/RingtonesSettings/RingtonesSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/components/RingtonesSettings/components/VolumeDropdown/VolumeDropdown.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetRow/PresetRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/PresetsSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/LabelWithTooltip/LabelWithTooltip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/LabelWithTooltip/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/PresetEditorDialog.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/components/ProjectTargetingField/ProjectTargetingField.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/components/ProjectTargetingField/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorSheet/PresetEditorSheet.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorSheet/components/ProjectTargetingField/ProjectTargetingField.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorSheet/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetsTable/PresetsTable.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/QuickAddPresets/QuickAddPresets.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2PresetsSection/V2PresetsSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/DeleteProjectSection/DeleteProjectSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/NameSection/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/ProjectLocationSection/ProjectLocationSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx
💤 Files with no reviewable changes (13)
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/V2AgentCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorSheet/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/LabelWithTooltip/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/LabelWithTooltip/LabelWithTooltip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/SystemThemeCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/SystemThemeCard/SystemThemeCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorSheet/PresetEditorSheet.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorSheet/components/ProjectTargetingField/ProjectTargetingField.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeCard/ThemeCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/V2AgentCard/V2AgentCard.tsx

Comment on lines +143 to +149
<SettingRow label="Email">
<Input
value={user.email}
readOnly
className="w-80 opacity-60"
/>
</SettingRow>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Email input has no accessible name — WCAG 1.3.1 Level A gap.

The SettingRow helper uses a plain <div> for the label with no htmlFor prop, and the Input elements have no id to form a programmatic association. The Email field is the most critical — it has neither a placeholder nor an aria-label, so assistive technologies have nothing to announce as the control's label. The Name input is partially covered by its placeholder, but a real <label> association is still required per WCAG.

OrganizationSettings.tsx's SettingsRow already solves this correctly with <Label htmlFor={htmlFor}> + matching id on each Input. Aligning SettingRow here to the same pattern fixes both the accessibility gap and the inconsistency.

🛡️ Proposed fix
-interface SettingRowProps {
+interface SettingRowProps {
   label: string;
   hint?: string;
+  htmlFor?: string;
   children: React.ReactNode;
 }

-function SettingRow({ label, hint, children }: SettingRowProps) {
+function SettingRow({ label, hint, htmlFor, children }: SettingRowProps) {
   return (
     <div className="flex items-center justify-between gap-8">
       <div className="flex-1 min-w-0">
-        <div className="text-sm font-medium">{label}</div>
+        <Label htmlFor={htmlFor} className="text-sm font-medium">{label}</Label>
         {hint && (
           <div className="text-xs text-muted-foreground mt-0.5">{hint}</div>
         )}
       </div>
       <div className="flex-shrink-0">{children}</div>
     </div>
   );
 }

Then add id/htmlFor at each call-site:

-<SettingRow label="Name">
+<SettingRow label="Name" htmlFor="account-name">
   <Input
+    id="account-name"
     value={nameValue}
     ...
   />
 </SettingRow>

-<SettingRow label="Email">
+<SettingRow label="Email" htmlFor="account-email">
   <Input
+    id="account-email"
     value={user.email}
     readOnly
     ...
   />
 </SettingRow>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx`
around lines 143 - 149, The Email input lacks a programmatic label association;
update SettingRow to render a proper <Label htmlFor={htmlFor}> (mirroring
OrganizationSettings' SettingsRow) and ensure each Input in AccountSettings
(e.g., the Email and Name inputs inside AccountSettings.tsx) receives a matching
id prop (unique per field) and the SettingRow call sites pass that same htmlFor
value; also remove reliance on placeholder for labeling and prefer the explicit
label/id pairing so assistive tech can announce the controls.

Comment on lines +11 to +14
<div className="flex items-center gap-3">
<Skeleton className="size-12 rounded-full" />
<Skeleton className="h-8 w-20" />
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Skeleton has a phantom rectangular placeholder that doesn't correspond to the rendered Avatar row.

The actual Avatar SettingRow in AccountSettings.tsx renders only a circular avatar button on the right side; there is no adjacent rectangular element. The h-8 w-20 skeleton (Line 13) appears to be a leftover from the previous design that had a separate "Change" button next to the avatar.

🔧 Proposed fix
 <div className="flex items-center gap-3">
   <Skeleton className="size-12 rounded-full" />
-  <Skeleton className="h-8 w-20" />
 </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex items-center gap-3">
<Skeleton className="size-12 rounded-full" />
<Skeleton className="h-8 w-20" />
</div>
<div className="flex items-center gap-3">
<Skeleton className="size-12 rounded-full" />
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/components/ProfileSkeleton/ProfileSkeleton.tsx`
around lines 11 - 14, ProfileSkeleton renders a phantom rectangular placeholder
that doesn't exist in the real Avatar SettingRow; in the ProfileSkeleton
component remove the extra Skeleton with className "h-8 w-20" (the rectangular
placeholder) so only the circular Skeleton remains and the markup matches
AccountSettings' Avatar row; reference the ProfileSkeleton component and ensure
the remaining Skeleton keeps its "size-12 rounded-full" classes and alignment
consistent with SettingRow in AccountSettings.tsx.

Comment on lines +97 to +101
const handleLabelBlur = () => {
if (label !== config.label && label.trim().length > 0) {
updateMutation.mutate({ label });
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

handleLabelBlur saves the untrimmed label string.

The guard uses label.trim().length > 0 but the mutation still sends the raw (possibly leading/trailing-whitespace) value.

🐛 Proposed fix
 const handleLabelBlur = () => {
-    if (label !== config.label && label.trim().length > 0) {
-        updateMutation.mutate({ label });
+    const trimmedLabel = label.trim();
+    if (trimmedLabel !== config.label && trimmedLabel.length > 0) {
+        updateMutation.mutate({ label: trimmedLabel });
     }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentDetail/AgentDetail.tsx`
around lines 97 - 101, handleLabelBlur currently checks trimmed length but calls
updateMutation.mutate with the raw label, which can persist leading/trailing
whitespace; change handleLabelBlur to pass the trimmed string to
updateMutation.mutate (e.g., compute const trimmed = label.trim() and call
updateMutation.mutate({ label: trimmed })) and keep the existing comparison
using config.label so you only update when the effective label changed.

Comment on lines +68 to +74
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 4 } }),
useSensor(TouchSensor, {
activationConstraint: { delay: 150, tolerance: 5 },
}),
useSensor(KeyboardSensor),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and get its content
find . -name "AgentsSettingsSidebar.tsx" -type f

Repository: superset-sh/superset

Length of output: 217


🏁 Script executed:

# Read the relevant section of the file
head -100 apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx | tail -50

Repository: superset-sh/superset

Length of output: 1472


🏁 Script executed:

# Check the imports at the top of the file
head -30 apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx

Repository: superset-sh/superset

Length of output: 682


🌐 Web query:

@dnd-kit/sortable KeyboardSensor sortableKeyboardCoordinates required

💡 Result:

In @dnd-kit/sortable, the KeyboardSensor requires the sortableKeyboardCoordinates coordinateGetter option to enable proper keyboard navigation for sortable items. Without it, the default KeyboardSensor moves items by a fixed pixel distance (25px), which doesn't align with sortable behavior. With it, arrow keys move the active item to the closest sortable element in the pressed direction within the same DndContext. Usage example from official docs: const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); This is shown consistently in the sortable preset documentation and source code. It is required for sortable keyboard dragging to work intuitively, preventing issues like page scrolling or incorrect movement.

Citations:


🌐 Web query:

@dnd-kit/sortable keyboard navigation coordinateGetter best practices

💡 Result:

For keyboard navigation in @dnd-kit/sortable, the best practice is to configure the KeyboardSensor with the sortableKeyboardCoordinates coordinateGetter. This custom getter moves the active sortable item to the closest sortable element in the direction of the arrow key pressed (Up, Down, Left, Right), rather than the default 25-pixel increments of the standard KeyboardSensor. Import from '@dnd-kit/sortable': import { sortableKeyboardCoordinates, verticalListSortingStrategy, // other imports } from '@dnd-kit/sortable'; Setup sensors: const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); Use in DndContext within a SortableContext: {/* sortable items */} Key benefits: - Handles variable item sizes and multiple containers. - Filters droppables by direction (e.g., only those below for Down arrow). - Uses closestCorners collision detection. - Adjusts coordinates based on scroll ancestors, same container checks, and item order. - Prevents default browser scrolling with event.preventDefault. Accessibility requirements: - Draggable activators must be focusable (e.g., button, role="button" tabindex=0). - Use Space/Enter to start/end drag (ARIA rule 3). - Customize screenReaderInstructions if changing default keys. Potential issues: - In scrollable containers or dropdowns, downward movement may stick or cause page scroll; mitigations include react-remove-scroll-bar or custom coordinateGetters. - For lists with variable heights, update state in onDragOver for optimal UX. This approach ensures ARIA-compliant keyboard accessibility for sortable lists.

Citations:


Wire the keyboard sensor to sortable coordinates.

KeyboardSensor without sortableKeyboardCoordinates breaks keyboard navigation in this sortable list. Without it, the sensor defaults to 25px movement increments instead of moving to the adjacent sortable item.

Suggested change
 import {
 	arrayMove,
 	SortableContext,
+	sortableKeyboardCoordinates,
 	useSortable,
 	verticalListSortingStrategy,
 } from "@dnd-kit/sortable";
@@
 	const sensors = useSensors(
 		useSensor(MouseSensor, { activationConstraint: { distance: 4 } }),
 		useSensor(TouchSensor, {
 			activationConstraint: { delay: 150, tolerance: 5 },
 		}),
-		useSensor(KeyboardSensor),
+		useSensor(KeyboardSensor, {
+			coordinateGetter: sortableKeyboardCoordinates,
+		}),
 	);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 4 } }),
useSensor(TouchSensor, {
activationConstraint: { delay: 150, tolerance: 5 },
}),
useSensor(KeyboardSensor),
);
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 4 } }),
useSensor(TouchSensor, {
activationConstraint: { delay: 150, tolerance: 5 },
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/V2AgentsSettings/components/AgentsSettingsSidebar/AgentsSettingsSidebar.tsx`
around lines 68 - 74, The KeyboardSensor used in useSensors is not wired to
sortableKeyboardCoordinates, causing keyboard navigation to use default 25px
increments instead of moving between sortable items; update the
useSensor(KeyboardSensor) call inside useSensors to pass
sortableKeyboardCoordinates as the coordinate getter (i.e.,
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })) so
KeyboardSensor uses the sortable list coordinates; ensure you import
sortableKeyboardCoordinates where AgentsSettingsSidebar defines useSensors and
keep the existing MouseSensor and TouchSensor options intact.

Comment on lines +259 to +270
<button
type="button"
onClick={handleLogoUpload}
disabled={!isOwner}
className="rounded-md transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-100"
aria-label="Change organization logo"
>
<OrganizationLogo
logo={logoPreview}
name={organization.name}
/>
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Logo button missing selectImageMutation.isPending guard — inconsistent with the avatar button pattern.

In AccountSettings.tsx the avatar button uses disabled={selectImageMutation.isPending} to block re-entry during the async upload flow. The logo button here only guards with disabled={!isOwner}, so a second click during the HTTP upload phase (after the Electron file dialog closes) will race with the in-flight request — producing duplicate toast.success calls and a non-deterministic final logoPreview value.

🐛 Proposed fix
 <button
   type="button"
   onClick={handleLogoUpload}
-  disabled={!isOwner}
+  disabled={!isOwner || selectImageMutation.isPending}
   className="rounded-md transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-100"
   aria-label="Change organization logo"
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
onClick={handleLogoUpload}
disabled={!isOwner}
className="rounded-md transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-100"
aria-label="Change organization logo"
>
<OrganizationLogo
logo={logoPreview}
name={organization.name}
/>
</button>
<button
type="button"
onClick={handleLogoUpload}
disabled={!isOwner || selectImageMutation.isPending}
className="rounded-md transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-100"
aria-label="Change organization logo"
>
<OrganizationLogo
logo={logoPreview}
name={organization.name}
/>
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/organization/components/OrganizationSettings/OrganizationSettings.tsx`
around lines 259 - 270, The logo button in OrganizationSettings is missing the
upload-in-progress guard, so update the button that calls handleLogoUpload
(rendering OrganizationLogo) to also disable while selectImageMutation.isPending
— e.g., change the disabled prop to combine the ownership check and the mutation
pending flag (disabled={!isOwner || selectImageMutation.isPending}) so it
matches the avatar/upload pattern in AccountSettings.tsx and prevents
re-entry/double toasts during the async upload flow.

Comment on lines +203 to +217
const handleBrowseDirectory = async () => {
const result = await selectDirectory.mutateAsync({
title: "Select preset directory",
defaultPath: browseDefaultPath,
});
if (!result.canceled && result.path) {
if (originWorkspace?.worktreePath) {
onDirectorySelect(
toPresetDirectoryValue(originWorkspace.worktreePath, result.path),
);
return;
}
onDirectorySelect(result.path);
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling around selectDirectory.mutateAsync.

If the IPC call throws (e.g., unavailable channel or unexpected Electron error), the exception bubbles up from the onClick handler as an unhandled promise rejection — the UI shows no feedback and the button is not automatically re-enabled. A try/catch keeps the interaction safe.

🛡️ Proposed fix
-	const handleBrowseDirectory = async () => {
-		const result = await selectDirectory.mutateAsync({
-			title: "Select preset directory",
-			defaultPath: browseDefaultPath,
-		});
-		if (!result.canceled && result.path) {
-			if (originWorkspace?.worktreePath) {
-				onDirectorySelect(
-					toPresetDirectoryValue(originWorkspace.worktreePath, result.path),
-				);
-				return;
-			}
-			onDirectorySelect(result.path);
-		}
-	};
+	const handleBrowseDirectory = async () => {
+		try {
+			const result = await selectDirectory.mutateAsync({
+				title: "Select preset directory",
+				defaultPath: browseDefaultPath,
+			});
+			if (!result.canceled && result.path) {
+				if (originWorkspace?.worktreePath) {
+					onDirectorySelect(
+						toPresetDirectoryValue(originWorkspace.worktreePath, result.path),
+					);
+					return;
+				}
+				onDirectorySelect(result.path);
+			}
+		} catch {
+			// IPC failure – dialog was not shown or channel is unavailable.
+			// selectDirectory.error will be set; nothing further to do.
+		}
+	};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection/components/PresetEditorDialog/PresetEditorDialog.tsx`
around lines 203 - 217, The handleBrowseDirectory function calls
selectDirectory.mutateAsync directly and can throw, so wrap the await call in a
try/catch inside handleBrowseDirectory (the function defined near
PresetEditorDialog) to prevent unhandled promise rejections; on error,
log/report the error (use existing logger or set an error state/notification)
and ensure any UI state that disables the browse button is reset so the button
is re-enabled, while keeping the successful-path behavior that calls
onDirectorySelect (including using toPresetDirectoryValue when
originWorkspace?.worktreePath exists).

Comment on lines +216 to +232
<button
type="button"
onClick={() => setShowSessionList((v) => !v)}
className="inline-flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
>
<HiChevronRight
className={cn(
"size-3 transition-transform",
showSessionList && "rotate-90",
)}
/>
<span
aria-hidden
className="size-1.5 rounded-full bg-emerald-500"
/>
{sessionCountLabel}
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Expand/collapse button is missing aria-expanded.

Screen readers can't determine the expansion state without it.

♿ Proposed fix
 <button
     type="button"
     onClick={() => setShowSessionList((v) => !v)}
+    aria-expanded={showSessionList}
     className="inline-flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/V2SessionsSection/V2SessionsSection.tsx`
around lines 216 - 232, The expand/collapse button in V2SessionsSection lacks an
aria-expanded attribute; update the button (the element that calls
setShowSessionList and uses showSessionList) to include
aria-expanded={showSessionList} so assistive tech can read the current state,
and optionally add aria-controls pointing to the session list's id (ensure the
session list element has a matching id) to link the button to the collapsible
region.

Comment on lines +18 to +42
const commit = () => {
const trimmed = value.trim();
if (!trimmed) {
setValue(currentName);
return;
}
if (trimmed === currentName) return;
projectActions.renameProject(projectId, trimmed);
};

return (
<Input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={commit}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
(e.target as HTMLInputElement).blur();
}
if (e.key === "Escape") {
e.preventDefault();
setValue(currentName);
(e.target as HTMLInputElement).blur();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Escape resets display but onBlurcommit() still commits the draft

When the user presses Escape:

  1. setValue(currentName) schedules a batched state update — value in the closure is still the draft.
  2. .blur() fires synchronously, triggering onBlurcommit().
  3. commit() reads the stale draft value, finds trimmed !== currentName, and calls renameProject(projectId, draft) — the opposite of the intended discard.

The fix is a synchronous ref flag, since refs are not batched:

🐛 Proposed fix
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";

 export function NameSection({ projectId, currentName }: NameSectionProps) {
   const { v2Projects: projectActions } = useOptimisticCollectionActions();
   const [value, setValue] = useState(currentName);
+  const escapingRef = useRef(false);

   useEffect(() => {
     setValue(currentName);
   }, [currentName]);

   const commit = () => {
+    if (escapingRef.current) {
+      escapingRef.current = false;
+      return;
+    }
     const trimmed = value.trim();
     if (!trimmed) {
       setValue(currentName);
       return;
     }
     if (trimmed === currentName) return;
     projectActions.renameProject(projectId, trimmed);
   };

   return (
     <Input
       value={value}
       onChange={(e) => setValue(e.target.value)}
       onBlur={commit}
       onKeyDown={(e) => {
         if (e.key === "Enter") {
           e.preventDefault();
           (e.target as HTMLInputElement).blur();
         }
         if (e.key === "Escape") {
           e.preventDefault();
+          escapingRef.current = true;
           setValue(currentName);
           (e.target as HTMLInputElement).blur();
         }
       }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/`$projectId/components/V2ProjectSettings/components/NameSection/NameSection.tsx
around lines 18 - 42, The Escape handler currently calls setValue(currentName)
(which is batched) then blur(), causing onBlur → commit() to read the stale
draft value and accidentally rename; fix by introducing a synchronous ref flag
(e.g., isDiscardRef) used by onKeyDown Escape to mark that the change was
discarded, and update commit() to check that ref and skip rename if set,
clearing the ref afterwards; reference the commit function, value/currentName
state, the onBlur handler, and the onKeyDown Escape branch to implement this
behavior.

Comment on lines +48 to +52
if (e.key === "Escape") {
e.preventDefault();
setValue(currentRepoCloneUrl ?? "");
(e.target as HTMLInputElement).blur();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Escape discards visual state but still commits the draft via onBlur

Same race condition as NameSection (see that file): setValue(currentRepoCloneUrl ?? "") schedules a batched state update, so when the synchronous .blur() fires onBlurcommit(), the value closure still holds the draft. If the draft differs from currentRepoCloneUrl, updateRepository is called with the wrong value.

🐛 Proposed fix using an escape ref
+import { useEffect, useRef, useState } from "react";
-import { useEffect, useState } from "react";
 export function RepositorySection({ ... }) {
   const { v2Projects: projectActions } = useOptimisticCollectionActions();
   const [value, setValue] = useState(currentRepoCloneUrl ?? "");
+  const escapingRef = useRef(false);
   const openUrl = electronTrpc.external.openUrl.useMutation();
   ...
-      onBlur={commit}
+      onBlur={() => {
+        if (escapingRef.current) {
+          escapingRef.current = false;
+          return;
+        }
+        commit();
+      }}
       onKeyDown={(e) => {
         if (e.key === "Enter") {
           e.preventDefault();
           (e.target as HTMLInputElement).blur();
         }
         if (e.key === "Escape") {
           e.preventDefault();
+          escapingRef.current = true;
           setValue(currentRepoCloneUrl ?? "");
           (e.target as HTMLInputElement).blur();
         }
       }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/`$projectId/components/V2ProjectSettings/components/RepositorySection/RepositorySection.tsx
around lines 48 - 52, The Escape handler currently does
setValue(currentRepoCloneUrl ?? "") then immediately triggers blur which invokes
onBlur → commit while the state update is still pending, causing commit to see
the old draft; modify the handler and related onBlur/commit logic to use an
"escapeRef" boolean flag (or similar) on keydown: when Escape is pressed, set
escapeRef.current = true, reset the input visually via
setValue(currentRepoCloneUrl ?? ""), call blur, and in onBlur/commit check
escapeRef.current—if true, clear the flag and skip calling
updateRepository/commit; ensure the flag is reset for normal blurs so other
paths still commit changes.

Kitenite added 3 commits May 4, 2026 01:38
# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx
#	apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
Click-the-thumbnail upload (drops the redundant Upload/Replace button) and
quieter ghost Remove, mirroring the AccountSettings avatar pattern. Switch
to FaGithub for consistency with RepositorySection.
Biome useSemanticElements: span[role=button] should just be button. Safe
here because the drag handle is a sibling of the row button, not nested
inside it — sibling DOM nodes don't propagate clicks.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx (1)

11-15: ⚡ Quick win

Use alias import for SettingsListSidebar instead of a relative path.

Line 11 currently uses a long relative import; switch this to the repo alias form for consistency with the rest of this file and desktop codebase.

♻️ Suggested diff
 import {
 	type SettingsListGroup,
 	settingsListItemClass,
 	SettingsListSidebar,
-} from "../../../components/SettingsListSidebar";
+} from "renderer/routes/_authenticated/settings/components/SettingsListSidebar";

As per coding guidelines, "apps/desktop/**/*.{ts,tsx}: Use alias as defined in tsconfig.json when possible".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx`
around lines 11 - 15, Replace the long relative import for
SettingsListSidebar/SettingsListGroup/settingsListItemClass with the repo
tsconfig alias import used elsewhere in this file; locate the import statement
that currently imports SettingsListSidebar, SettingsListGroup, and
settingsListItemClass and change its module specifier to the project alias form
(the same alias pattern used by other imports in this file) so the three symbols
are imported via the configured path alias instead of the deep relative path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx`:
- Around line 11-15: Replace the long relative import for
SettingsListSidebar/SettingsListGroup/settingsListItemClass with the repo
tsconfig alias import used elsewhere in this file; locate the import statement
that currently imports SettingsListSidebar, SettingsListGroup, and
settingsListItemClass and change its module specifier to the project alias form
(the same alias pattern used by other imports in this file) so the three symbols
are imported via the configured path alias instead of the deep relative path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60b2e2dc-1d79-4d90-8354-a57e5692f47e

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc1780 and e2b2b0d.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/routes/_authenticated/settings/projects/components/ProjectsSettingsSidebar/ProjectsSettingsSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/IconUploadField/IconUploadField.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx

@Kitenite Kitenite merged commit da2fd34 into main May 4, 2026
5 of 8 checks passed
@Kitenite Kitenite deleted the settings-cleanup-v1 branch May 4, 2026 08:46
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request May 21, 2026
…et-sh#4035)

* refactor(desktop): restyle Account settings page

Replace shadcn Card with bordered row-stack pattern (matching 1Code).
Bump avatar to 48px with explicit Change button instead of hover-pencil.
Fix right-column width inconsistency (avatar w-8 vs inputs flex-1) by
giving every row a fixed w-80 control. Wrap Sign out in its own box so
the page reads as two parallel containers instead of card + bare button.

* refactor(desktop): polish Appearance settings page

- Sentence case all section headings (Markdown style, Editor font,
  Terminal font, Import theme, Download base file)
- Standardize header rhythm: h3 mb-1 + xs description mb-3
- Declutter Theme header: fold Marketplace and Docs links into a
  natural-language description line
- Markdown: merge the trailing Tufte note into the section description
  so the section ends with the control, not prose
- Font Reset button: ghost text-xs muted -> outline sm so it's
  discoverable next to the controls
- Add aria-label to font size Input and Markdown Select for a11y

* refactor(desktop): horizontally center settings content

Wrap the Outlet in a centered max-width container so every settings page
sits in the middle of the content pane instead of pinned to the left edge
of the sidebar. The pages' own max-w-4xl becomes redundant but harmless;
new pages get centered for free.

* refactor(desktop): polish Notifications settings page

- Sentence case headings and button labels (Notification sound, Add /
  Replace custom audio)
- Broaden subtitle so it covers toggle + volume + ringtone, not just
  the picker
- Drop the dangling tip block at the bottom; fold the format hint into
  the section description under Notification sound
- Swap HiPlus -> HiArrowPath when the button means Replace, so the icon
  matches the action
- Drop the decorative speaker icon from the Volume label so it matches
  every other setting label on the page
- Tighten section gap from space-y-8 to space-y-6 to match Behavior

* refactor(desktop): polish RingtoneCard selection state

- Drop the ring around selected cards: border-2 + ring-2 + check was
  three signals for one state. Now border-primary + check + a subtle
  footer tint, single-thickness border so cards don't shift on select.
- Promote the play button: bigger (h-9), centered in the preview area
  with a shadow, so it reads as the primary affordance instead of a
  corner hint. Emoji moves below the button as visual identity.
- Move duration inline with the name in the footer (it's metadata, not
  decoration) and drop the floating top-right badge.
- Add aria-label and aria-pressed for keyboard / SR users.

* fix(desktop): move ringtone play button back to corner

Centered play overlapping the emoji read as a single broken element,
not as two distinct affordances. Restore the corner placement and the
4xl emoji. Keep the other RingtoneCard wins from the previous commit
(no ring on selected, inline duration, aria labels).

* refactor(desktop): swap RingtoneCard play/check positions

Check moves to top-right of the preview area as a small badge so the
emoji area stays uncluttered. Play moves into the footer alongside the
name + duration, where it sits next to the metadata it acts on. Both
affordances now have a single, predictable home each.

* style(desktop): soften selected ringtone border to primary/40

* refactor(desktop): remove dead telemetry UI from General settings

Drops the always-hidden ({false && ...}) telemetry toggle and its
useQuery/useMutation/handler plumbing from BehaviorSettings, plus the
matching SETTING_ITEM_ID, variant gate, and search index entry. The
underlying getTelemetryEnabled tRPC procedure stays because
TelemetrySync still consumes it at boot to set the PostHog opt state.

* refactor(desktop): polish Keyboard shortcuts settings page

- Match other settings pages: text-xl page title, sentence case
  ("Keyboard shortcuts"), p-6 max-w-4xl w-full wrapper order.
- Drop the inner max-h-[calc(100vh-320px)] scroll: it doubled up with
  the parent settings pane scroll and made the page feel cramped.
  Parent now scrolls naturally.
- Drop the loud uppercase Command/Shortcut column header inside each
  category card -- the rows are clearly label + chip, no header needed.
- Promote "Reset all" from ghost to outline so it reads as a page-level
  action, distinct from the per-row Reset buttons.
- Recording state now visually highlights the active row (subtle
  destructive-tinted bg + ring on the trigger) and changes the trigger
  text from "Recording..." to "Press a key...".

* refactor(desktop): polish Git & worktrees settings page

Sentence case headings ("Git & worktrees", "Branch prefix") and tighten
the Branch prefix description so it explains the setting in addition
to showing the preview. WorktreeLocationPicker is left untouched
because v1 ProjectSettings still consumes it.

* refactor(desktop): polish v2 Agents settings page

- Page wrapper max-w-5xl -> max-w-4xl: the layout already caps at 4xl
  so the wider value was silently dead. Match the rest of settings.
- Reset to defaults: ghost -> outline so it reads as a page-level
  action distinct from row-level controls (matches Keyboard's Reset
  all).
- Friendlier zero-state: dashed-border card with a Bot icon, "No
  agents yet" heading, and a hint pointing at the Add menu, instead
  of a single bare line of muted text.
- Loading: skeleton cards that mirror the V2AgentCard layout instead
  of "Loading agent settings...".
- Drop the empty grey square placeholder when a preset has no icon
  (in both the card and the Add-agent dropdown). Empty rectangles
  read as broken; absence reads as intentional.

* refactor(desktop): tone down V2AgentCard styling

Card was reading as a "toy" -- big rounded shadow on every row, app
icon at 32px, semibold title, big primary-blue argv/stdin pills.

- Card: rounded-xl + shadow-sm + py-6/gap-6 -> rounded-lg, no shadow,
  no internal gap. Flatter, less floating widget energy.
- Header: text-base semibold title -> text-sm medium title; description
  text-sm -> text-xs muted; preset icon size-8 -> size-7. Header
  padding p-4 -> p-3 to fit the smaller type.
- Drag handle: bare icon with no affordance -> icon in a hover-bg pill
  with a slightly muted default color so it reads as draggable.
- Trash: hover -> destructive tint to make the destructive action read
  as destructive only on intent.
- Padding alignment: expanded CardContent inherited shadcn px-6 (24px)
  while the header was p-4 (16px), so fields were inset further than
  the title above them. Drop CardContent and use plain px-4 pb-4.
- Field grouping: insert a border-t between Label (identity) and the
  three launch fields so the form has a readable two-section structure.
- Field labels shrunk to text-xs and inputs use text-xs font-mono so
  the dense launch settings fit without feeling oversized.
- Argv/stdin: replace two primary-blue pill buttons with a true
  segmented control (single bordered group, accent-tinted selected
  half). Reads as one decision, not two competing buttons.
- Tighten copy: drop "(empty)" placeholder for "--prompt"; compress
  the argv/stdin explanation into one line.

* refactor(desktop): collapse V2 agent cards into a single bordered list

1Code's settings list pattern is one bordered container with divide-y
rows -- no individual cards per item. Agents had 8 separate Cards
floating with space-y-3 between them, which made each row look like
its own widget instead of a list entry.

- V2AgentsSettings: wrap configs in
  `rounded-lg border bg-card divide-y` instead of `space-y-3`.
- V2AgentCard: drop the per-item <Card> wrapper. The row sits flat
  inside the parent container; hover bg lifts via hover:bg-muted/50
  to match 1Code AccountRow.
- Drag state: when isDragging, lift the row via bg-accent/40 + z-10
  so it visually sits above the rest of the list during reorder.

Less floating-widget energy, denser, reads as a settings list.

* style(desktop): drop bg-card from V2 agents list container

* style(desktop): pad expanded V2 agent content from header

* refactor(desktop): adopt 1Code horizontal-row pattern for V2 agent fields

The expanded panel previously stacked Label/Input/description vertically
for each field. 1Code's settings forms (model config, account config,
etc.) put label + hint on the left and the control on the right at a
fixed w-80, separated by border-t. Same row vocabulary as the rest of
settings, so the expanded content reads continuous with the page
instead of like a popup form.

- New local AgentField helper renders one row: label + hint left,
  control (w-80, shrink-0) right, separated by border-t.
- All four fields (Label, Command, Prompt-only args, Prompt transport)
  use the new pattern.
- Drop the previous nested grouping divider; row dividers carry the
  separation now.
- Promote field labels back to text-sm so they line up visually with
  the agent's title in the header above.

* refactor(desktop): convert ringtone picker to bordered list rows

Same pattern as the v2 agents list: one bordered container with
divide-y rows, no per-item bg, no per-item chrome. Each ringtone is
one row with [emoji] [name + duration on top, description below]
[play button] [check]. Sound selection is a list-of-options task,
not visual comparison, so cards were wasting vertical space and
making each option feel like a widget instead of a list entry.

* refactor(desktop): polish v2 Terminal settings page

- Page wrapper max-w-7xl -> max-w-4xl: layout caps at 4xl already, so
  the wider value was dead. Match the rest of settings.
- Sentence case "Terminal presets" / "Add preset" header.
- Drop the orphan "Click a preset row to edit details." hint that sat
  below the table -- click affordance is discoverable from the row
  hover state and editor sheet open.

* fix(desktop): put play button to the right of the check in ringtone rows

* refactor(desktop): collapse theme section into 1Code-style row + dropdown

Replaces the 3-column ThemeCard grid (and split-preview SystemThemeCard) with a
single bordered list: one Theme row with a Select dropdown, and Light/Dark rows
that slide in only when System is selected. Each option shows a small Aa swatch
in the picker. ThemeCard / SystemThemeCard components are no longer needed.

* refactor(desktop): unify theme section header into the bordered list

* style(desktop): drop divider lines between appearance sections

* refactor(desktop): redesign terminal presets list

Drop the uppercase column header + striped rows. PresetsTable becomes a
bordered list with divide-y matching Agents/Ringtones. PresetRow shows
name + workspace/tab badges on top, command snippet (truncated, with +N
overflow indicator) below, and a right-side meta column with applies-to
+ mode. Visibility eye and chevron sit at the right edge. QuickAddPresets
becomes a tighter chip strip with rounded-full pills.

* refactor(desktop): unify v2 presets into one card and quiet the rows

V2PresetsSection now wraps header, quick-add chips and the preset list
into one bordered card with divide-y separators, matching the new theme
section. PresetsTable gains a `bordered` prop so v1 keeps its own border.

PresetRow trims the right side: chevron is gone (the whole row already
clicks), applies-to + mode collapse to a single muted line, and the
visibility eye fades in on row hover when the preset is visible.

* refactor(desktop): convert PresetEditorSheet to a centered Dialog

Renames PresetEditorSheet -> PresetEditorDialog and switches the side
sheet to a max-w-2xl modal Dialog matching 1Code's MCP/agent edit
patterns. The body now uses a 2-column grid for short fields (Name +
Description) and full-width sections for Commands, Applies-to,
Directory, Launch mode, and a bordered Auto-run group.

Drops the custom field/control className overrides, the uppercase
"Advanced" subheading, the question-mark tooltips, and the
LabelWithTooltip helper. Fields use a small Field helper with a
hint underneath instead.

* style(desktop): widen PresetEditorDialog to max-w-4xl

* refactor(desktop): two-column layout for PresetEditorDialog body

The dialog now uses both halves of its width: left column groups the
recipe (Name, Description, Commands), right column groups the launch
behavior (Applies to, Directory, Launch mode, Auto-run). Drops the
DialogDescription (the title carries enough context), drops the
bordered Auto-run group, and shortens the multi-command launch-mode
labels.

* chore(desktop): drop unused DialogDescription import in PresetEditorDialog

* refactor(desktop): single column PresetEditorDialog with settings rows

Drops the cluttered two-column form. Width returns to max-w-2xl
(matching 1Code's MCP edit dialog footprint) and the body is single
column:

  - Top: Name, Description, Commands as labeled fields
  - Bottom: bordered settings list with rows for Applies to, Directory,
    Launch mode, plus two Switch rows for the auto-run options

Launch mode now uses a single Select dropdown for both single- and
multi-command cases (replacing the radio group). Auto-run uses Switches
in their own rows instead of two checkboxes packed under one label.

* refactor(desktop): unify PresetEditorDialog rows on the V2AgentCard pattern

The whole body becomes one bordered card with divide-y rows. Each row
follows V2AgentCard's AgentField shape: label + hint on the left
(min-w-0 flex-1), control on the right (w-80 shrink-0). Multi-line
controls (Commands editor, ProjectTargetingField) use a stacked variant
of the same row so the label sits on top and the editor spans the full
width.

Also collapses Directory's text "Browse" button to an icon-only button
so the input gets the row's full control width.

* style(desktop): drop the inner card border around PresetEditorDialog rows

* style(desktop): drop row dividers in PresetEditorDialog

* refactor(desktop): tighter row controls in PresetEditorDialog

- Applies to: stacked radio + popover -> segmented (All / Specific)
  with the project picker only appearing under Specific. Inline row.
- Launch mode: single-command case becomes a Segmented control
  (Current tab / New tab); multi-command keeps the Select for the
  longer labels.
- Auto-run: collapses two Switch rows into one row with two checkbox
  options (On workspace creation / On new tab).
- Tighten DialogRow padding from p-4 to py-2.5 (no horizontal padding)
  and the right-side control width from w-80 to w-72.

* revert(desktop): bring back two Switch rows for Auto-run

* revert(desktop): bring back stacked radios for Applies to

* style(desktop): restore segmented Applies to in an inline row

* style(desktop): more breathing room between PresetEditorDialog rows

* refactor(desktop): drop bordered cards in Links settings

Each LinkTierMapper section becomes a flat title + description + 3
inline rows (label + smaller Select). Matches the borderless settings
section style we converged on elsewhere.

* refactor(desktop): redesign Models settings around the lighter section style

Each provider is one borderless SettingsSection: small icon + name +
description on the left, status badge + Sign in / Sign out on the right.
Below sits the API key ConfigRow (label on top, input + Save/Clear
inline). The old standalone "Override Provider" collapsible folds into
Anthropic as an inline Advanced collapsible holding Auth token, Base
URL, and Additional env vars.

Sentence case throughout (API key, Auth token, Additional env vars),
swaps Connect/Logout to Sign in/Sign out, drops the rounded-xl border +
bg-card chrome, and softens row title weight from font-semibold to
font-medium to match Appearance/Links.

* refactor(desktop): horizontal ConfigRow + Active badge + ghost Sign out

ConfigRow lifts the AgentField horizontal pattern: label+description on
the left, the input + Save/Clear buttons on the right at w-80. When the
API key is the active credential, an inline 'Active' badge sits next to
the row's label. Sign out drops to the ghost variant so it visually
steps back next to a primary Sign in.

* Revert "refactor(desktop): horizontal ConfigRow + Active badge + ghost Sign out"

This reverts commit d227690.

* refactor(desktop): simpler Anthropic Advanced section in Models

The three override fields all save into the same env config, so per-row
Save/Clear buttons were redundant. Switch to save-on-blur for Auth token,
Base URL, and Additional env vars; replace the per-row buttons with a
single 'Reset' link that appears in the Advanced trigger row when any
field has content. Adds a small 'Saved on blur' hint at the bottom.

* chore(desktop): drop unused hasAnthropicConfig in ModelsSettings

* refactor(desktop): align Organization settings with the rest of settings

Standardize page wrapper to p-6 max-w-4xl and the title to text-xl.
Drop the Card/CardContent wrapper and convert the org info rows to the
flat SettingsRow pattern used elsewhere. Replace the hover-pencil
overlay on Logo and Slug with visible "Change" buttons. Members table
keeps its border but loses the heavy text-lg heading in favor of a
small section header with hint.

* style(desktop): drop dividers between Organization info rows

* style(desktop): drop explicit Change buttons in Organization rows

The logo image itself is now the trigger for the file picker (cursor +
hover-fade + ring-on-focus). The slug input is read-only and clicking
or pressing Enter/Space opens the SlugDialog, which still owns the
format check + availability check + URL-changes-confirmation flow.

* chore(desktop): drop unused Button import in OrganizationSettings

* style(desktop): drop Change button on Account avatar; click avatar to upload

* style(desktop): align project settings section heading with the rest

Soften the shared SettingsSection heading from text-base font-semibold
to text-sm font-medium so V2 project pages match the section heading
style used in Appearance, Models, etc. Sentence-case the V2 project
section titles ("Host Service Location" -> "Project location",
"Danger Zone" -> "Danger zone") and tighten the descriptions.

* style(desktop): flatten integrations settings rows

Drop the heavyweight Card layout for borderless rows that match the rest of settings: smaller icon, description below name, status dot + 'Connected to X' / 'Not connected' inline next to the Manage/Connect button. Sentence case throughout.

* style(desktop): standardize billing settings page

Apply the shared settings vocabulary: text-xl page title with sentence-cased description, drop the manual border-border/60 + shadow-none on the hero plan/upgrade cards (use defaults), refactor BillingDetails into 3 borderless DetailRows with Edit on right, slim RecentInvoices into a divide-y list with always-visible View link.

* style(desktop): standardize API keys settings page

Match the rest of settings: p-6 max-w-4xl wrapper, text-xl page title, sentence case throughout. Move Generate key into the header row, drop the second redundant subtitle, swap the Table for a flat divide-y list with hover-revealed Revoke. Drop the bespoke amber warning box in the new-key dialog for a one-line muted hint.

* style(desktop): flatten billing plan + upgrade into borderless rows

Drop the two heavy bordered hero cards (CurrentPlan + Upgrade) for a single 'Plan' section with two divide-y rows that match BillingDetails and RecentInvoices below. Drop the Upgrade card's feature grid and View-all-plans button (the All plans link in the page header already covers this); keep a single Upgrade button on the right.

* style(desktop): give Pro and Enterprise plans a premium accent

Pro gets a violet→blue gradient on the plan name + violet sparkles icon. Enterprise gets an amber→rose gradient + amber sparkles. Free stays plain. The Upgrade button on the free-plan upsell row also takes the violet→blue gradient so the destination tier reads visually before you even click it.

* style(desktop): revert gradient billing accent to a monochrome pill

The violet/amber gradient + sparkles read as cheap. Replaced with a small high-contrast monochrome pill (Linear/Notion-style) — bg-foreground text-background, uppercase, tight tracking. Single restrained signal that the tier is paid; the Upgrade button goes back to the default primary style.

* refactor(desktop): tighten v2 project settings sections

RepositorySection: drop the Edit/Save/Cancel state machine for save-on-blur on a single Input — type, blur to save, Esc to revert. ProjectLocationSection: drop the redundant 'This device' label column (the section header already covers it); show path or 'Not set up' across the full row with the action button to its right. DeleteProjectSection: lift to label + hint + button so it reads consistently with other settings rows.

* feat(desktop): rename / GitHub link / softer delete on v2 project settings

Drawing from 1Code's project tab: add a Name section at the top with save-on-blur rename (was previously read-only via the page header). Repository row now shows a small 'Open' button with the GitHub icon when the URL parses to a github.com remote — opens the repo in the browser. Soften the destructive Delete button to outline + destructive hover so it's clearly dangerous without dominating the page; the AlertDialog still gates the actual delete.

* style(desktop): bump section descriptions to text-sm + tighter copy

1Code uses text-sm muted descriptions in settings; ours used text-xs which read as cramped. Bump the shared SettingsSection helper to text-sm (touches v1 too — acceptable per v1-sunset). Shorten the v2 project descriptions to label-style phrases ('Display name for this project') instead of conversational ones.

* style(desktop): tighter v2 project settings layout

Drop the redundant 'Danger zone' heading — the Delete row already self-describes; replace with a thin border-t separator. Drop the descriptive subtitles on Name and Repository (the labels + placeholders cover it; only Project location keeps its description since state matters). Tighten section spacing space-y-8 → space-y-6.

* style(desktop): tighter agent detail layout to match v2 project

Drop redundant section descriptions on Label and Launch (the labels + placeholders cover them). Tighten section spacing space-y-10 → space-y-6. Replace the 'Delete this agent' Section heading with a thin border-t separator + label+hint+button row, matching the v2 project Delete pattern.

* style(desktop): align host settings with v2 project vocabulary

Members heading text-base → text-sm to match every other settings section. Drop the redundant 'People in your organization who can use this host' description (the heading + table covers it). Surface the 'Only owners can change membership' hint up under the heading when the viewer isn't an owner, instead of as a footer below the table. HostHeader: drop the inline <code> tag for the machine ID, use a font-mono span; trim 'machine ID' to just the ID after the status separator.

* style(desktop): align daemon section heading with the rest of settings

Replace the section heading <Label> with an <h3 className='text-sm font-medium'> so it matches every other settings section. Bump the description to text-sm and trim the verbose copy ('Owns every PTY session and survives app restarts.' instead of two sentences explaining what a PTY is). Status row, action buttons, monochrome 'Update available' pill, and AlertDialogs all already match the vocabulary — left alone.

* style(desktop): chevron-disclosure for daemon sessions; surface unavailable state

Drop the manual Refresh button — refetchOnWindowFocus + the 5s poll while the list is open already cover this. Replace the 'Show sessions / Hide sessions' action button with a chevron disclosure on the status row itself: chevron + dot + count becomes one clickable element when there are sessions to show. Color-code the daemon-unavailable case with destructive (red dot + destructive text) so it reads as a problem rather than a benign 'no sessions' state.

* style(desktop): hoist daemon action buttons into the section header

Update daemon and Force restart move from a row below the status into the right side of the section header — same pattern as Hosts > Members + Add member button. Frees up vertical space and reads top-down: heading + actions, then status + disclosure, then expanded session table.

* fix(desktop): align daemon status row to text baseline

items-center centers each child's box vertically — but the chevron + dot inside the button have different box metrics than the plain font-mono version span next to it, so the text baselines end up offset. items-baseline aligns the actual text baselines so '1 session running', 'v0.1.4', and the 'Update available' pill all sit on the same line.

* style(desktop): move daemon version next to heading; drop session-table border

Version moves from the status row up next to 'Manage daemon' as inline metadata (small font-mono, normal weight, muted) — version is a property of the daemon itself, not part of session state. Drop the rounded-md border wrapping the expanded sessions table; the table sits directly in the section without a contained box.

* other styles

* revert(desktop): drop last-visited settings page persistence

Did not work in practice. Restore the simple Account redirect.

* style(desktop): align v2 project icon field with settings vocabulary

Click-the-thumbnail upload (drops the redundant Upload/Replace button) and
quieter ghost Remove, mirroring the AccountSettings avatar pattern. Switch
to FaGithub for consistency with RepositorySection.

* fix(desktop): use real <button> for agent drag handle

Biome useSemanticElements: span[role=button] should just be button. Safe
here because the drag handle is a sibling of the row button, not nested
inside it — sibling DOM nodes don't propagate clicks.
MocA-Love added a commit to MocA-Love/superset that referenced this pull request May 21, 2026
…ngs vocabulary

superset-sh#4035 introduced settings vocabulary unification (superset-sh#4035 [HUGE 73f]) and a
new clickPolicy-driven LinkTierMapper API. Several fork-specific
settings were not in upstream's SETTING_ITEM_ID enum, the v1->v2
migration UI relies on trpc procedures the fork does not implement,
and superset-sh#3823's iconUrl field is not yet adopted.

settings-search.ts: add 21 fork-specific SETTING_ITEM_ID entries
(APPEARANCE_VIBRANCY, BEHAVIOR_LANGUAGE_DIAGNOSTICS,
BEHAVIOR_RIGHT_SIDEBAR_OPEN_VIEW_WIDTH, GIT_SMART_COMMIT,
GIT_AUTO_STASH, GIT_BRANCH_SORT_ORDER, GIT_POST_COMMIT_COMMAND,
METRICS_GITHUB_OVERVIEW, METRICS_GITHUB_TRAFFIC, METRICS_GITHUB_COPY,
PROJECT_AUTO_IMPORT_WORKTREES, PROJECT_AUTO_REMOVE_WORKTREES,
RINGTONES_AIVIS, RINGTONES_AIVIS_DICTIONARY, RINGTONES_AIVIS_USAGE,
SERVICE_STATUS_PROVIDERS, TERMINAL_SUGGESTIONS,
VSCODE_EXTENSIONS_MANAGE, VSCODE_EXTENSIONS_INDENT_RAINBOW,
VSCODE_EXTENSIONS_TRAILING_SPACES, VSCODE_EXTENSIONS_REFERENCE_GRAPH)
with matching SETTING_ITEM_VARIANT entries.

LinkTierMapper.tsx + index.ts: revert to fork's pre-superset-sh#3988 actionLabels
API (no renderer/lib/clickPolicy yet, which arrives with Phase 5 superset-sh#3988).

ExperimentalSettings.tsx: drop v1->v2 migration UI block. The
underlying useMigrateV1DataToV2 hook calls trpc procedures
(readV1WorkspaceSections / listState / upsertState) the fork removed.
Keep EXPERIMENTAL_SUPERSET_V2 toggle. EXPERIMENTAL_V1_MIGRATION
constant stays in settings-search so existing search keywords remain
stable. Re-introduce the migration UI and re-wire the missing trpc
procedures when v1->v2 migration is intentionally added back.

useMigrateV1DataToV2/: re-delete entire hook directory (upstream
attempted to restore it via superset-sh#4035 vocabulary cleanup paths).

V2ProjectSettings.tsx: drop the Appearance / IconUploadField section
(superset-sh#3823 iconUrl arrives in cycle 41).

ProjectsSettingsSidebar.tsx: drop iconUrl from ProjectRow + select,
pass githubOwner={null} to ProjectThumbnail (will be restored in cycle 41).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant