Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ui/goose2/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Guidelines for AI agents (and developers) working on this codebase.

## Project Overview

Goose2 is a Tauri 2 + React 19 desktop app. It uses TypeScript strict mode, Vite, and Tailwind CSS 3. The codebase follows a feature-sliced architecture organized under `src/app/`, `src/features/`, and `src/shared/`.
Goose2 is a Tauri 2 + React 19 desktop app. It uses TypeScript strict mode, Vite, and Tailwind CSS 4. The codebase follows a feature-sliced architecture organized under `src/app/`, `src/features/`, and `src/shared/`.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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


## First Steps

Expand Down Expand Up @@ -99,7 +99,7 @@ ThemeProvider manages three axes:
| Axis | Values | Persistence | Mechanism |
|--------------|---------------------------------|-----------------|----------------------------------------------|
| Theme mode | `light`, `dark`, `system` | localStorage | `.dark` class on `<html>` |
| Accent color | Any hex value | localStorage | `--color-accent` CSS variable |
| Accent color | Hex color | localStorage | `--brand` / `--color-brand` CSS variables |
| Density | `compact`, `comfortable`, `spacious` | localStorage | `--density-spacing` CSS variable (0.75/1/1.25) |

- CSS variables are defined in `globals.css` with light/dark variants.
Expand Down
80 changes: 80 additions & 0 deletions ui/goose2/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,86 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script>
(() => {
const getDefaultAccent = (theme) =>
theme === "dark" ? "#ffffff" : "#1a1a1a";

const normalizeHexColor = (color) => {
const value = color?.trim();
if (!value || value === "default") return null;

const hex = value.startsWith("#") ? value.slice(1) : value;
if (/^[0-9a-fA-F]{3}$/.test(hex)) {
return `#${hex
.split("")
.map((char) => char + char)
.join("")
.toLowerCase()}`;
}
if (/^[0-9a-fA-F]{6}$/.test(hex)) {
return `#${hex.toLowerCase()}`;
}

return null;
};

const getRelativeLuminance = (hexColor) => {
const hex = hexColor.slice(1);
const channels = [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)]
.map((channel) => {
const value = Number.parseInt(channel, 16) / 255;
return value <= 0.04045
? value / 12.92
: ((value + 0.055) / 1.055) ** 2.4;
});

return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
};

const getContrastColor = (hexColor) => {
const luminance = getRelativeLuminance(hexColor);
const blackContrast = (luminance + 0.05) / 0.05;
const whiteContrast = 1.05 / (luminance + 0.05);
return blackContrast >= whiteContrast ? "#000000" : "#ffffff";
};

try {
const root = document.documentElement;
const storedTheme = localStorage.getItem("goose-theme") || "system";
const resolvedTheme =
storedTheme === "system"
? window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
: storedTheme;
const accent =
normalizeHexColor(localStorage.getItem("goose-accent-color")) ||
getDefaultAccent(resolvedTheme);
const foreground = getContrastColor(accent);
const density = localStorage.getItem("goose-density") || "comfortable";
const spacingScale = {
compact: "0.75",
comfortable: "1",
spacious: "1.25",
};

root.classList.add(resolvedTheme === "dark" ? "dark" : "light");
root.style.colorScheme = resolvedTheme === "dark" ? "dark" : "light";
root.style.setProperty("--brand", accent);
root.style.setProperty("--brand-foreground", foreground);
root.style.setProperty("--color-brand", accent);
root.style.setProperty("--color-brand-foreground", foreground);
root.style.accentColor = accent;
root.style.setProperty(
"--density-spacing",
spacingScale[density] || spacingScale.comfortable,
);
} catch {
// ThemeProvider applies the canonical theme state after React mounts.
}
})();
</script>
<title>Goose</title>
</head>
<body>
Expand Down
4 changes: 2 additions & 2 deletions ui/goose2/src/features/agents/ui/AvatarDropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ export function AvatarDropZone({
className={cn(
"size-16 overflow-hidden border-2 bg-muted shadow-sm",
isDragOver
? "scale-105 border-accent bg-accent/15 shadow-md ring-4 ring-accent/20"
: "border-border hover:border-border hover:bg-accent",
? "scale-105 border-brand bg-brand/10 shadow-md ring-4 ring-brand/20"
: "border-border hover:border-brand/50 hover:bg-brand/10",
disabled && "opacity-70 cursor-not-allowed",
isUploading && "animate-pulse",
)}
Expand Down
6 changes: 3 additions & 3 deletions ui/goose2/src/features/settings/ui/AgentProviderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ export function AgentProviderCard({ provider }: AgentProviderCardProps) {
return (
<div className="mt-3 space-y-2 border-t pt-3">
<div className="flex items-center gap-2">
<Spinner className="size-3.5 text-accent" />
<Spinner className="size-3.5 text-brand" />
<div className="min-w-0 flex-1">
<span className="text-xs font-medium">{phaseLabel}</span>
{stepInfo && (
Expand All @@ -420,7 +420,7 @@ export function AgentProviderCard({ provider }: AgentProviderCardProps) {
<div
className={cn(
"flex flex-col rounded-lg border bg-background p-3 transition-colors",
isActive && "border-accent/50",
isActive && "border-brand/50 bg-brand/10",
)}
>
<div className="flex items-start justify-between">
Expand Down Expand Up @@ -451,7 +451,7 @@ export function AgentProviderCard({ provider }: AgentProviderCardProps) {
setupError
? "bg-danger"
: isActive
? "bg-accent animate-pulse"
? "bg-brand animate-pulse"
: "bg-muted-foreground/40",
)}
/>
Expand Down
35 changes: 29 additions & 6 deletions ui/goose2/src/features/settings/ui/AppearanceSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const ACCENT_COLORS = [
{ name: "red", value: "#ef4444" },
{ name: "pink", value: "#ec4899" },
{ name: "purple", value: "#a855f7" },
{ name: "indigo", value: "#6366f1" },
];

const DENSITY_OPTIONS = [
Expand Down Expand Up @@ -53,8 +52,15 @@ function SettingRow({

export function AppearanceSettings() {
const { t } = useTranslation("settings");
const { theme, setTheme, accentColor, setAccentColor, density, setDensity } =
useTheme();
const {
theme,
setTheme,
accentColorPreference,
resetAccentColor,
setAccentColor,
density,
setDensity,
} = useTheme();

return (
<SettingsPage title={t("appearance.title")}>
Expand Down Expand Up @@ -87,21 +93,38 @@ export function AppearanceSettings() {
label={t("appearance.accent.label")}
description={t("appearance.accent.description")}
>
<div className="grid grid-cols-4 gap-2">
<div className="flex max-w-36 flex-wrap justify-end gap-2">
<button
type="button"
title={t("appearance.accent.colors.default")}
aria-label={t("appearance.accent.colors.default")}
onClick={resetAccentColor}
className={cn(
"relative flex h-7 w-7 items-center justify-center overflow-hidden rounded-full border border-border transition-transform hover:scale-110",
accentColorPreference === "default" &&
"ring-2 ring-ring ring-offset-2 ring-offset-background",
)}
>
<span className="absolute inset-0 bg-[linear-gradient(135deg,#1a1a1a_0_50%,#ffffff_50%_100%)]" />
{accentColorPreference === "default" && (
<Check className="relative h-4 w-4 rounded-full bg-background p-0.5 text-foreground shadow-sm" />
)}
</button>
{ACCENT_COLORS.map((color) => (
<button
type="button"
key={color.value}
title={t(`appearance.accent.colors.${color.name}`)}
aria-label={t(`appearance.accent.colors.${color.name}`)}
onClick={() => setAccentColor(color.value)}
className={cn(
"flex h-7 w-7 items-center justify-center rounded-full transition-transform hover:scale-110",
accentColor === color.value &&
accentColorPreference === color.value &&
"ring-2 ring-ring ring-offset-2 ring-offset-background",
)}
style={{ backgroundColor: color.value }}
>
{accentColor === color.value && (
{accentColorPreference === color.value && (
<Check className="h-3.5 w-3.5 text-white" />
)}
</button>
Expand Down
2 changes: 1 addition & 1 deletion ui/goose2/src/features/settings/ui/ModelProviderPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function InventorySyncMessage({
role="status"
className="flex items-center gap-2 text-xs text-muted-foreground"
>
<IconLoader2 className="size-3 animate-spin text-accent" />
<IconLoader2 className="size-3 animate-spin text-brand" />
<span>{t("providers.loadingModels")}</span>
</p>
);
Expand Down
6 changes: 3 additions & 3 deletions ui/goose2/src/features/settings/ui/ModelProviderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ export function ModelProviderRow({
)}
{authenticating ? (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Spinner className="size-3.5 text-accent" />
<Spinner className="size-3.5 text-brand" />
<span>{t("providers.waitingForSignIn")}</span>
</div>
) : null}
Expand Down Expand Up @@ -486,10 +486,10 @@ export function ModelProviderRow({
<IconCheck className="size-4 flex-shrink-0 text-success" />
) : null}
{inventorySyncing ? (
<Spinner className="size-3.5 flex-shrink-0 text-accent" />
<Spinner className="size-3.5 flex-shrink-0 text-brand" />
) : null}
{!isConnected && authenticating ? (
<Spinner className="size-3.5 flex-shrink-0 text-accent" />
<Spinner className="size-3.5 flex-shrink-0 text-brand" />
) : null}
</button>

Expand Down
2 changes: 1 addition & 1 deletion ui/goose2/src/features/settings/ui/ProvidersSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ export function ProvidersSettings() {
</h4>
{loading ? (
<span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
<Spinner className="size-3 text-accent" />
<Spinner className="size-3 text-brand" />
{t("providers.models.checkingStatus")}
</span>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion ui/goose2/src/shared/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"colors": {
"blue": "Blue",
"cyan": "Cyan",
"default": "Default",
"green": "Green",
"indigo": "Indigo",
"orange": "Orange",
"pink": "Pink",
"purple": "Purple",
Expand Down
2 changes: 1 addition & 1 deletion ui/goose2/src/shared/i18n/locales/es/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"colors": {
"blue": "Azul",
"cyan": "Cian",
"default": "Predeterminado",
"green": "Verde",
"indigo": "Índigo",
"orange": "Naranja",
"pink": "Rosa",
"purple": "Morado",
Expand Down
Loading
Loading