Skip to content

feat: redesign settings panel — sidebar navigation, pill tags, toggle switches, repo cards - #56

Merged
sam-phinizy merged 5 commits into
mainfrom
feat/settings-redesign
Apr 2, 2026
Merged

feat: redesign settings panel — sidebar navigation, pill tags, toggle switches, repo cards#56
sam-phinizy merged 5 commits into
mainfrom
feat/settings-redesign

Conversation

@sam-phinizy

Copy link
Copy Markdown
Contributor

Summary

Replace the single-panel settings form with a professional sidebar-navigated settings panel.

New UI components:

  • TagInput.svelte — pill tag editor with Enter/comma to add, × to remove, backspace on empty, monospace variant
  • ToggleSwitch.svelte — accessible toggle with amber/grey states

Three categories:

  • General — author name + pill tag editors for labels and ignored folders
  • Git & GitHub — path picker with Browse for checkout location, repo cards with delete, manual add form, "Add from disk" with auto-detection of GitHub remote
  • Notifications — toggle switches with descriptions

New Rust command:

  • get_git_remote_url — reads the origin remote URL from a local git repo (used by "Add from disk" flow)

New utility:

  • parseGitHubRepo — extracts owner/repo from HTTPS and SSH GitHub URLs

Test plan

  • Open settings (Cmd+,)
  • Verify sidebar with three categories, General active by default
  • Switch between categories — verify content changes, active state updates
  • General: edit author name, add/remove labels via pill tag input, add/remove ignored folders
  • Git & GitHub: browse for checkout location, view existing tracked repos as cards
  • Git & GitHub: add a repo manually (type owner/repo + path)
  • Git & GitHub: click "Add from disk" — pick a git repo folder, verify auto-detection
  • Notifications: toggle switches on/off, verify amber/grey states
  • Save and reopen — verify all settings persisted
  • Cancel — verify no changes saved
  • Escape closes dialog, Cmd+Enter saves

🤖 Generated with Claude Code

sam-phinizy and others added 4 commits April 1, 2026 20:32
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Tauri command to read the git origin remote URL from a local repo,
plus a TypeScript utility to parse GitHub owner/repo from remote URLs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the 380px single-panel form with a 680px sidebar-navigated
settings dialog. Three categories (General, Git & GitHub, Notifications)
are navigable via a left sidebar. Tracked repos use card-based UI with
add-from-disk auto-detection. Labels and ignored folders use TagInput.
Notification toggles use ToggleSwitch components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 2, 2026 00:37
Global token bumps:
- --text-primary: 0.9 → 0.92
- --text-secondary: 0.6 → 0.65
- --text-muted: 0.3 → 0.42
- --text-ghost: 0.15 → 0.25

Settings panel:
- Section headers: ghost → muted, 10px → 11px
- Field labels: muted → secondary, 11px → 12px
- Inputs: 12px → 13px
- Hints: ghost → muted, 10px → 11px
- Toggle labels: 12px → 13px
- Toggle descriptions: ghost → muted, 10px → 11px

Launch screen:
- Tip text: ghost → muted, 12px → 13px
- Hero hint: ghost → muted, 12px → 13px
- Session card time: ghost → muted
- Session card meta: ghost → muted, 11px → 12px
- Column labels: muted → secondary, 11px → 12px

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

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.

Pull request overview

This PR redesigns the Settings dialog into a sidebar-navigated panel with reusable UI controls and adds a Tauri command to auto-detect GitHub remotes when adding repos from disk.

Changes:

  • Replaced the single settings form with a 3-category (General / Git & GitHub / Notifications) sidebar layout and new repo-card based tracked repo UI.
  • Added reusable TagInput (pill editor) and ToggleSwitch components for a denser, more modern settings UX.
  • Added get_git_remote_url (Rust) + getGitRemoteUrl (TS) and parseGitHubRepo to support “Add from disk” repo detection.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/lib/utils/parseGitRemote.ts Adds parseGitHubRepo helper to extract owner/repo from remote URLs.
src/lib/tauri.ts Adds getGitRemoteUrl() invoke wrapper for the new Rust command.
src/components/ui/ToggleSwitch.svelte New toggle switch component for Notifications.
src/components/ui/TagInput.svelte New pill-tag editor component for labels/ignored folders.
src/components/SettingsDialog.svelte Major settings UI redesign: sidebar navigation, repo cards, tag inputs, toggle rows, “Add from disk” flow.
src-tauri/src/lib.rs Registers the new get_git_remote_url command.
src-tauri/src/commands/git.rs Implements get_git_remote_url using git2 to read the origin remote URL.
docs/superpowers/specs/2026-03-30-settings-redesign-design.md Adds a design spec documenting the new settings layout and flows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +90
const remoteUrl = await getGitRemoteUrl(selected);
if (remoteUrl) {
const parsed = parseGitHubRepo(remoteUrl);
if (parsed) {
trackedReposList = [...trackedReposList, { repo: parsed, localPath: selected }];

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

In addRepoFromDisk, the tracked repo is saved with localPath: selected, but get_git_remote_url uses Repository::discover, so selected may be a subdirectory of the repo. Downstream code opens tracked repos via Repository::open(local_path), which will fail if local_path is not the repo root. Consider resolving the git root (e.g., via the existing get_git_root command or by returning the discovered workdir from Rust) and storing that path instead of the raw selection.

Copilot uses AI. Check for mistakes.
Comment on lines +143 to +144
<label>Author name</label>
<input bind:value={author} />

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The "Author name" <label> is no longer associated with its <input> (no for/id, and the input is not nested inside the label). This breaks screen reader labeling and also causes existing tests that use findByLabelText("Author name") to fail. Add an id on the input and for on the label (or wrap the input in the label).

Suggested change
<label>Author name</label>
<input bind:value={author} />
<label for="author-name">Author name</label>
<input id="author-name" bind:value={author} />

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +164
<label>Default labels</label>
<TagInput
tags={labelsList}
onAdd={(tag) => { labelsList = [...labelsList, tag]; }}
onRemove={(i) => { labelsList = labelsList.filter((_, idx) => idx !== i); }}
placeholder="Add label..."
/>
</div>
<div class="settings-field">
<label>Ignored folders</label>
<TagInput
tags={ignoredFoldersList}
onAdd={(tag) => { ignoredFoldersList = [...ignoredFoldersList, tag]; }}
onRemove={(i) => { ignoredFoldersList = ignoredFoldersList.filter((_, idx) => idx !== i); }}
placeholder="Add folder..."
mono

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

TagInput is rendered under a plain <label> that isn't programmatically connected to the actual <input> inside TagInput, so the tag editor has no accessible name. Consider adding a prop to TagInput to pass an aria-label/aria-labelledby through to its inner <input> (and wire the label up here), so screen readers can identify the control.

Suggested change
<label>Default labels</label>
<TagInput
tags={labelsList}
onAdd={(tag) => { labelsList = [...labelsList, tag]; }}
onRemove={(i) => { labelsList = labelsList.filter((_, idx) => idx !== i); }}
placeholder="Add label..."
/>
</div>
<div class="settings-field">
<label>Ignored folders</label>
<TagInput
tags={ignoredFoldersList}
onAdd={(tag) => { ignoredFoldersList = [...ignoredFoldersList, tag]; }}
onRemove={(i) => { ignoredFoldersList = ignoredFoldersList.filter((_, idx) => idx !== i); }}
placeholder="Add folder..."
mono
<label id="default-labels-label">Default labels</label>
<TagInput
tags={labelsList}
onAdd={(tag) => { labelsList = [...labelsList, tag]; }}
onRemove={(i) => { labelsList = labelsList.filter((_, idx) => idx !== i); }}
placeholder="Add label..."
aria-labelledby="default-labels-label"
/>
</div>
<div class="settings-field">
<label id="ignored-folders-label">Ignored folders</label>
<TagInput
tags={ignoredFoldersList}
onAdd={(tag) => { ignoredFoldersList = [...ignoredFoldersList, tag]; }}
onRemove={(i) => { ignoredFoldersList = ignoredFoldersList.filter((_, idx) => idx !== i); }}
placeholder="Add folder..."
mono
aria-labelledby="ignored-folders-label"

Copilot uses AI. Check for mistakes.
<div class="settings-repo-name">{repo.repo}</div>
<div class="settings-repo-path">{repo.localPath}</div>
</div>
<button class="settings-repo-remove" onclick={() => removeRepo(i)}>&times;</button>

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The repo remove button only contains "×" and has no accessible name. Add an aria-label (e.g., "Remove tracked repo") so screen readers can announce the action.

Suggested change
<button class="settings-repo-remove" onclick={() => removeRepo(i)}>&times;</button>
<button
class="settings-repo-remove"
onclick={() => removeRepo(i)}
aria-label="Remove tracked repo"
>&times;</button>

Copilot uses AI. Check for mistakes.
<input bind:value={newRepoName} placeholder="owner/repo" />
<div class="settings-path-picker-inline">
<input bind:value={newRepoPath} placeholder="/path/to/checkout" />
<button onclick={browseRepoPath}>&#x1F4C1;</button>

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The folder browse button is icon-only (📁) and has no accessible name. Add an aria-label (e.g., "Browse for repository path") so it’s usable with assistive tech.

Suggested change
<button onclick={browseRepoPath}>&#x1F4C1;</button>
<button onclick={browseRepoPath} aria-label="Browse for repository path">&#x1F4C1;</button>

Copilot uses AI. Check for mistakes.
Comment on lines +211 to +215
<div>Agent replied to annotation</div>
<div class="settings-toggle-desc">When an agent responds to one of your comments</div>
</div>
<ToggleSwitch checked={notifyAnnotationReply} onToggle={() => notifyAnnotationReply = !notifyAnnotationReply} />
</div>

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

Each ToggleSwitch is adjacent to visible text, but the switch itself has no accessible name (aria-label/aria-labelledby). For role="switch" controls, ensure the switch is labeled (e.g., pass an ariaLabel prop or wire aria-labelledby to the text element here).

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +12
let activeCategory = $state<"general" | "git" | "notifications">("general");

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

This redesign introduces new UI behaviors (sidebar category switching, tag editing, repo card add/remove, "Add from disk" flow). There is an existing SettingsDialog.test.ts in the repo that asserts on the old labeled inputs/buttons; it will now fail and does not cover the new flows. Update/add tests to match the new structure and mock getGitRemoteUrl + the dialog picker.

Copilot uses AI. Check for mistakes.
{#each tags as tag, i}
<span class="tag-pill">
{tag}
<button class="tag-remove" onclick={() => onRemove(i)}>&times;</button>

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The tag remove button only renders "×" and has no accessible name. Add an aria-label (e.g., "Remove tag") and set type="button" so it can’t accidentally submit a surrounding form.

Suggested change
<button class="tag-remove" onclick={() => onRemove(i)}>&times;</button>
<button
class="tag-remove"
type="button"
aria-label="Remove tag"
onclick={() => onRemove(i)}
>
&times;
</button>

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +16
onToggle,
}: {
checked: boolean;
onToggle: () => void;
} = $props();
</script>

<button
class="toggle-switch"
class:toggle-switch-on={checked}
onclick={onToggle}
role="switch"
aria-checked={checked}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

ToggleSwitch renders a <button role="switch"> without an accessible name and without type="button". For accessibility, expose aria-label/aria-labelledby (or accept a label prop) so the switch can be named by consumers; adding type="button" prevents accidental form submission when used inside a form.

Suggested change
onToggle,
}: {
checked: boolean;
onToggle: () => void;
} = $props();
</script>
<button
class="toggle-switch"
class:toggle-switch-on={checked}
onclick={onToggle}
role="switch"
aria-checked={checked}
onToggle,
label,
ariaLabel,
ariaLabelledby,
}: {
checked: boolean;
onToggle: () => void;
label?: string;
ariaLabel?: string;
ariaLabelledby?: string;
} = $props();
</script>
<button
class="toggle-switch"
class:toggle-switch-on={checked}
type="button"
onclick={onToggle}
role="switch"
aria-checked={checked}
aria-label={ariaLabel ?? label}
aria-labelledby={ariaLabelledby}

Copilot uses AI. Check for mistakes.
@sam-phinizy
sam-phinizy merged commit cf75946 into main Apr 2, 2026
6 of 15 checks passed
@sam-phinizy
sam-phinizy deleted the feat/settings-redesign branch April 2, 2026 00:46
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.

2 participants