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,40 @@
/**
* @file settingsSearch.test.ts
* @description `rankSettings`: an empty query matches nothing, a section title
* outranks an entry title, which outranks a description and a keyword, exact
* beats prefix beats substring, and a query matching nothing returns nothing.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, expect, it } from "vitest";
import { rankSettings, type SettingsEntry } from "../settingsSearch";
const entries: SettingsEntry[] = [
{ section: "pricing", title: "Pricing", sectionTitle: true },
{ section: "claude", title: "Claude Code", sectionTitle: true },
{ section: "claude", title: "Model Pricing" },
{ section: "about", title: "About", description: "Server pricing info" },
{ section: "kw", title: "Other", keywords: ["pricing"] },
{ section: "prefix", title: "Pricing tables", sectionTitle: true },
];
describe("rankSettings", () => {
it("returns nothing for an empty or blank query", () => {
expect(rankSettings("", entries)).toEqual([]);
expect(rankSettings(" ", entries)).toEqual([]);
});
it("orders by field tier, then exact > prefix > substring", () => {
expect(rankSettings("Pricing", entries)).toEqual([
{ section: "pricing", score: 900 },
{ section: "prefix", score: 850 },
{ section: "claude", score: 600 },
{ section: "about", score: 400 },
{ section: "kw", score: 300 },
]);
});
it("returns nothing when no field matches", () => {
expect(rankSettings("zzz", entries)).toEqual([]);
});
});