feat: edit in the Changes panel, git graph history, plan quota (v2.3.0)
- Changes panel: a working file opens in a CodeMirror editor with its diff inline (DiffEditor.tsx, @codemirror/merge, lazy-loaded); Changes folds the unchanged lines, Whole file shows them; side by side at 50/50 and full width; a Revert per block; original from HEAD or the branch's merge base (`mergeBase` on GET /git/changes?range=branch) - Every diff: green = line added whole, red = removed whole, blue = edited in place (--diff-mod) - History drawn as a commit graph (#58: `parents` on GET /git/log, --date-order; lib/commitGraph.ts) - Per-workspace never-stage list, drawn orange in the Files tree (--kept); closed Terminal rows past three deleted - Status bar plan quota for Claude (per account) and Codex (GET /api/pricing/quota, server/lib/quota.js); compact hover panel that stays open, Refresh button with a spinner - Settings → Appearance holds language, theme and pet; Ctrl+Shift+C copies; compact sidebar navigation and short session times
This commit is contained in:
@@ -9,7 +9,8 @@
|
||||
* element with that `id` or `data-settings-anchor` and briefly highlights it.
|
||||
* Back returns to the page before Settings when there is one, else /workspace;
|
||||
* an unknown section shows a skeleton until the tool list loads, then toasts
|
||||
* and falls back to General. A tool's own settings live under `agents/<id>` — Claude Code and
|
||||
* and falls back to Appearance (which also holds the language and the pet;
|
||||
* `general` and `pet` redirect there). A tool's own settings live under `agents/<id>` — Claude Code and
|
||||
* Codex have their own pages, every other installed, enabled catalog tool the
|
||||
* generic `AiToolConfig` — and a tool's page is hidden while that tool is
|
||||
* disabled. A workspace's own settings live on its home, not here.
|
||||
@@ -32,18 +33,12 @@ import {
|
||||
Database,
|
||||
Info,
|
||||
Bot,
|
||||
Boxes,
|
||||
Coins,
|
||||
GitBranch,
|
||||
KeyRound,
|
||||
Orbit,
|
||||
Palette,
|
||||
PawPrint,
|
||||
Puzzle,
|
||||
Router,
|
||||
Search,
|
||||
Settings as SettingsIcon,
|
||||
SquareTerminal,
|
||||
} from "lucide-react";
|
||||
import { Settings } from "./Settings";
|
||||
import { CcConfig } from "./CcConfig";
|
||||
@@ -52,7 +47,6 @@ import { AiToolConfig } from "./AiToolConfig";
|
||||
import { AiRouterConfig } from "./AiRouterConfig";
|
||||
import { GitCredentialsConfig } from "./GitCredentialsConfig";
|
||||
import { AppearanceSettings } from "../components/settings/AppearanceSettings";
|
||||
import { PetSettings } from "../components/settings/PetSettings";
|
||||
import { AiToolsSettings } from "../components/settings/AiToolsSettings";
|
||||
import { PricingSettings } from "../components/settings/PricingSettings";
|
||||
import { WorktreeVisibilitySettings } from "../components/settings/WorktreeVisibilitySettings";
|
||||
@@ -64,6 +58,7 @@ import {
|
||||
type SettingsEntry,
|
||||
} from "../lib/settingsSearch";
|
||||
import { Skeleton } from "../components/Skeleton";
|
||||
import { AiToolIcon } from "../components/AiToolIcon";
|
||||
import { toast } from "../lib/toast";
|
||||
|
||||
const GROUPS = ["general", "agents", "routing", "cost", "git"] as const;
|
||||
@@ -80,7 +75,7 @@ interface Section {
|
||||
|
||||
/** The pre-1.9 `?tool=` ids, as section ids. Anything else was a catalog tool. */
|
||||
const LEGACY_TOOL: Record<string, string> = {
|
||||
dashboard: "general",
|
||||
dashboard: "appearance",
|
||||
"claude-code": "agents/claude-code",
|
||||
codex: "agents/codex",
|
||||
"ai-router": "ai-router",
|
||||
@@ -104,13 +99,29 @@ const historyIdx = (): number => {
|
||||
export const backDelta = (current: number, entry: number): number =>
|
||||
-Math.max(1, current - entry + 1);
|
||||
|
||||
/** An unknown section: say so, then show General. */
|
||||
/** Sections folded into Appearance: old links and bookmarks still land. */
|
||||
/** A tool's own mark as a nav icon, one stable component per tool id. */
|
||||
const navIcons = new Map<string, ComponentType<{ className?: string }>>();
|
||||
function toolNavIcon(id: string, label: string) {
|
||||
let Icon = navIcons.get(id);
|
||||
if (!Icon) {
|
||||
Icon = ({ className }) => (
|
||||
<AiToolIcon tool={{ id, label }} className={`${className ?? ""} !bg-transparent !p-0`} />
|
||||
);
|
||||
navIcons.set(id, Icon);
|
||||
}
|
||||
return Icon;
|
||||
}
|
||||
|
||||
const MOVED: Record<string, string> = { general: "appearance", pet: "appearance#pet" };
|
||||
|
||||
/** An unknown section: say so, then show Appearance. */
|
||||
function UnknownSection({ name }: { name: string }) {
|
||||
const { t } = useTranslation("settings");
|
||||
useEffect(() => {
|
||||
toast.info(t("settingsNav.unknownSection", { section: name }));
|
||||
}, [name, t]);
|
||||
return <Navigate to="/settings/general" replace />;
|
||||
return <Navigate to="/settings/appearance" replace />;
|
||||
}
|
||||
|
||||
export function SettingsHub() {
|
||||
@@ -191,13 +202,6 @@ export function SettingsHub() {
|
||||
description: t(`settingsNav.section.${key}.description`),
|
||||
});
|
||||
const sections: Section[] = [
|
||||
{
|
||||
id: "general",
|
||||
group: "general",
|
||||
Icon: SettingsIcon,
|
||||
...s("general"),
|
||||
render: () => <Settings section="general" />,
|
||||
},
|
||||
{
|
||||
id: "data",
|
||||
group: "general",
|
||||
@@ -219,13 +223,6 @@ export function SettingsHub() {
|
||||
...s("appearance"),
|
||||
render: () => <AppearanceSettings />,
|
||||
},
|
||||
{
|
||||
id: "pet",
|
||||
group: "general",
|
||||
Icon: PawPrint,
|
||||
...s("pet"),
|
||||
render: () => <PetSettings />,
|
||||
},
|
||||
{
|
||||
id: "notifications",
|
||||
group: "general",
|
||||
@@ -245,7 +242,7 @@ export function SettingsHub() {
|
||||
{
|
||||
id: "agents/claude-code",
|
||||
group: "agents" as const,
|
||||
Icon: Boxes,
|
||||
Icon: toolNavIcon("claude", "Claude Code"),
|
||||
...s("claudeCode"),
|
||||
render: () => <CcConfig />,
|
||||
},
|
||||
@@ -256,7 +253,7 @@ export function SettingsHub() {
|
||||
{
|
||||
id: "agents/codex",
|
||||
group: "agents" as const,
|
||||
Icon: SquareTerminal,
|
||||
Icon: toolNavIcon("codex", "Codex"),
|
||||
...s("codex"),
|
||||
render: () => <CodexConfig />,
|
||||
},
|
||||
@@ -269,7 +266,7 @@ export function SettingsHub() {
|
||||
.map((x) => ({
|
||||
id: `agents/${x.id}`,
|
||||
group: "agents" as const,
|
||||
Icon: x.id === "antigravity" ? Orbit : Puzzle,
|
||||
Icon: toolNavIcon(x.id, x.label),
|
||||
title: x.label,
|
||||
description: t("settingsNav.section.tool.description", { tool: x.label }),
|
||||
render: () => <AiToolConfig key={x.id} toolId={x.id} />,
|
||||
@@ -349,9 +346,10 @@ export function SettingsHub() {
|
||||
? "agents"
|
||||
: tool
|
||||
? (LEGACY_TOOL[tool] ?? `agents/${tool}`)
|
||||
: "general";
|
||||
: "appearance";
|
||||
return <Navigate to={`/settings/${to}`} replace />;
|
||||
}
|
||||
if (MOVED[rest]) return <Navigate to={`/settings/${MOVED[rest]}`} replace />;
|
||||
const active = sections.find((x) => x.id === rest);
|
||||
if (!active) {
|
||||
// A catalog tool page exists only once the tool list has loaded.
|
||||
|
||||
Reference in New Issue
Block a user