feat(settings): one page per section with a grouped nav and search

Settings moves from a tab bar to /settings/<section>: a left nav grouped
General / Agents / Routing / Git / Projects (a select on narrow screens), a
search box over every section (Ctrl/Cmd+F, Enter opens the first match), and
row primitives. Each AI tool has its page under agents/<id>, the AI Tools
modal becomes the Agents page, Pricing shows every vendor, Git → Worktrees
sets which untracked worktrees the sidebar lists, and each project gets a
page with its own worktree override and Forget all. ?tool= and #ai-tools
links redirect to the path form.
This commit is contained in:
2026-10-01 16:55:44 +07:00
parent ba2c4e1914
commit eb90244182
33 changed files with 2478 additions and 2594 deletions
@@ -0,0 +1,61 @@
/**
* @file WorktreeVisibilitySettings — Settings → Git → Worktrees: whether the
* sidebar's project tree lists a repo's untracked worktrees, and which sources
* of them (`.claude/worktrees/*`, AgentHub-managed) it hides. Writes the tree's
* own localStorage prefs (`lib/projectTreePrefs.ts`), so the sidebar follows at
* once; a project's own override lives on its Settings → Projects page.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useTranslation } from "react-i18next";
import { useProjectTreePrefs, writeProjectTreePrefs } from "../../lib/projectTreePrefs";
import { SettingsSection, SettingsSwitchRow } from "./SettingsRow";
export const WORKTREE_SOURCES = ["claude", "agenthub"] as const;
/** Show/hide switches for each source over a `hideSources` list. */
export function SourceSwitches({
hidden,
onChange,
disabled = false,
}: {
hidden: string[];
onChange: (hidden: string[]) => void;
disabled?: boolean;
}) {
const { t } = useTranslation("settings");
return (
<>
{WORKTREE_SOURCES.map((src) => (
<SettingsSwitchRow
key={src}
label={t(`worktrees.${src}`)}
description={t(`worktrees.${src}Desc`)}
checked={!hidden.includes(src)}
disabled={disabled}
onChange={(show) => onChange(show ? hidden.filter((s) => s !== src) : [...hidden, src])}
/>
))}
</>
);
}
export function WorktreeVisibilitySettings() {
const { t } = useTranslation("settings");
const prefs = useProjectTreePrefs();
return (
<SettingsSection title={t("worktrees.title")} description={t("worktrees.description")}>
<SettingsSwitchRow
label={t("worktrees.showUntracked")}
description={t("worktrees.showUntrackedDesc")}
checked={prefs.showUntracked}
onChange={(showUntracked) => writeProjectTreePrefs({ showUntracked })}
/>
<SourceSwitches
hidden={prefs.hideSources}
disabled={!prefs.showUntracked}
onChange={(hideSources) => writeProjectTreePrefs({ hideSources })}
/>
</SettingsSection>
);
}