mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Fix settings page (#17)
* fix: repair broken settings modal layout The <nav> element with settings navigation buttons was a sibling of the sidebar div instead of being nested inside it, and the main flex container was missing its closing tag. This broke the sidebar/content flex layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore missing padding wrapper around Settings title The merge in #15 dropped a wrapper div with px-4 py-4 padding and entrance animation around the "Settings" heading, causing it to be smashed against the top-left corner of the sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: consolidate types and stores into feature-sliced architecture Removed root-level src/types/ and src/stores/ directories that contained outdated duplicates of types and unused store implementations. Moved unique config types (AgentConfig, Skill, Provider, Model) to src/shared/types/config.ts. Relocated stores to their owning features (agentConfigStore → agents, sessionStore → chat, skillStore → skills). Deduplicated ChatState, TokenState, and Session types — src/shared/types/chat.ts is now canonical. Updated AGENTS.md with clear guidelines on where types and stores should live. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -19,6 +19,10 @@ src/
|
||||
api/ — Backend API integration (when feature calls backend)
|
||||
types.ts — Feature-specific type definitions (when needed)
|
||||
shared/
|
||||
types/ — Canonical shared type definitions (single source of truth)
|
||||
agents.ts — Agent, Persona, Provider types
|
||||
chat.ts — ChatState, TokenState, Session, SSE events
|
||||
messages.ts — Message, MessageContent, type guards
|
||||
ui/ — Reusable UI components (button, etc.)
|
||||
lib/ — Utilities (cn.ts for class merging)
|
||||
theme/ — Theme provider, appearance settings
|
||||
@@ -27,7 +31,6 @@ src/
|
||||
api/ — API integration
|
||||
constants/ — Shared constants
|
||||
context/ — Shared contexts
|
||||
types/ — Shared type definitions (single source of truth)
|
||||
```
|
||||
|
||||
### Feature Organization
|
||||
@@ -133,3 +136,5 @@ ThemeProvider manages three axes:
|
||||
- Don't skip `type="button"` on buttons.
|
||||
- Don't use color-only indicators without text alternatives.
|
||||
- Never use `--no-verify` when pushing — fix the underlying lint/hook issues.
|
||||
- Don't create root-level `src/types/` or `src/stores/` directories — types belong in `src/shared/types/`, stores belong in `src/features/<feature>/stores/`.
|
||||
- Don't duplicate type definitions across files — each type has one canonical location.
|
||||
|
||||
@@ -100,133 +100,144 @@ export function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
"flex w-44 flex-col border-r bg-background-secondary/50 transition-all duration-700 ease-out",
|
||||
isLoaded ? "opacity-100 translate-x-0" : "opacity-0 -translate-x-2",
|
||||
)}
|
||||
>
|
||||
<h2 className="text-sm font-semibold">Settings</h2>
|
||||
</div>
|
||||
<nav className="flex flex-col gap-1 px-2">
|
||||
{NAV_ITEMS.map((item, index) => (
|
||||
<button
|
||||
type="button"
|
||||
key={item.id}
|
||||
onClick={() => setActiveSection(item.id)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-all duration-600 ease-out",
|
||||
activeSection === item.id
|
||||
? "bg-background text-foreground shadow-sm"
|
||||
: "text-foreground-secondary hover:bg-background-secondary/50 hover:text-foreground duration-300",
|
||||
isLoaded
|
||||
? "opacity-100 translate-x-0"
|
||||
: "opacity-0 translate-x-4",
|
||||
)}
|
||||
style={{
|
||||
transitionDelay: isLoaded ? "0ms" : `${index * 40 + 300}ms`,
|
||||
}}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative flex-1 overflow-y-auto">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 z-10 rounded-md p-1 text-foreground-secondary transition-colors hover:text-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"px-6 py-4 transition-all duration-400 ease-out",
|
||||
isTransitioning
|
||||
? "opacity-0 translate-y-2"
|
||||
: "opacity-100 translate-y-0",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all duration-600 ease-out",
|
||||
"px-4 py-4 transition-all duration-500 ease-out",
|
||||
isLoaded
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 translate-y-4",
|
||||
? "opacity-100 translate-x-0"
|
||||
: "opacity-0 -translate-x-2",
|
||||
)}
|
||||
style={{
|
||||
transitionDelay: isLoaded ? "400ms" : "0ms",
|
||||
}}
|
||||
>
|
||||
{activeSection === "appearance" && <AppearanceSettings />}
|
||||
{activeSection === "general" && (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">General</h3>
|
||||
<p className="mt-1 text-sm text-foreground-secondary">
|
||||
General settings will appear here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeSection === "projects" && (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Projects</h3>
|
||||
<p className="mt-1 text-sm text-foreground-secondary">
|
||||
Manage your projects.
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold">Settings</h2>
|
||||
</div>
|
||||
<nav className="flex flex-col gap-1 px-2">
|
||||
{NAV_ITEMS.map((item, index) => (
|
||||
<button
|
||||
type="button"
|
||||
key={item.id}
|
||||
onClick={() => setActiveSection(item.id)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-all duration-600 ease-out",
|
||||
activeSection === item.id
|
||||
? "bg-background text-foreground shadow-sm"
|
||||
: "text-foreground-secondary hover:bg-background-secondary/50 hover:text-foreground duration-300",
|
||||
isLoaded
|
||||
? "opacity-100 translate-x-0"
|
||||
: "opacity-0 translate-x-4",
|
||||
)}
|
||||
style={{
|
||||
transitionDelay: isLoaded ? "0ms" : `${index * 40 + 300}ms`,
|
||||
}}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Archived Projects */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">Archived Projects</h3>
|
||||
{!loadingArchived && archivedProjects.length === 0 && (
|
||||
<p className="text-xs text-foreground-secondary">
|
||||
No archived projects.
|
||||
{/* Content */}
|
||||
<div className="relative flex-1 overflow-y-auto">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 z-10 rounded-md p-1 text-foreground-secondary transition-colors hover:text-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"px-6 py-4 transition-all duration-400 ease-out",
|
||||
isTransitioning
|
||||
? "opacity-0 translate-y-2"
|
||||
: "opacity-100 translate-y-0",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all duration-600 ease-out",
|
||||
isLoaded
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 translate-y-4",
|
||||
)}
|
||||
style={{
|
||||
transitionDelay: isLoaded ? "400ms" : "0ms",
|
||||
}}
|
||||
>
|
||||
{activeSection === "appearance" && <AppearanceSettings />}
|
||||
{activeSection === "general" && (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">General</h3>
|
||||
<p className="mt-1 text-sm text-foreground-secondary">
|
||||
General settings will appear here.
|
||||
</p>
|
||||
)}
|
||||
{archivedProjects.map((project) => (
|
||||
<div
|
||||
key={project.id}
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-2"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
className="inline-block w-2 h-2 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: project.color }}
|
||||
/>
|
||||
<span className="text-sm truncate">{project.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRestore(project.id)}
|
||||
className="px-2 py-1 text-xs font-medium rounded-md border border-border hover:bg-background-tertiary transition-colors"
|
||||
>
|
||||
Restore
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeletingProject(project)}
|
||||
className="px-2 py-1 text-xs font-medium rounded-md text-foreground-danger hover:bg-background-danger/10 transition-colors"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeSection === "about" && (
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">About</h3>
|
||||
<p className="mt-1 text-sm text-foreground-secondary">
|
||||
About information will appear here.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{activeSection === "projects" && (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Projects</h3>
|
||||
<p className="mt-1 text-sm text-foreground-secondary">
|
||||
Manage your projects.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Archived Projects */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">Archived Projects</h3>
|
||||
{!loadingArchived && archivedProjects.length === 0 && (
|
||||
<p className="text-xs text-foreground-secondary">
|
||||
No archived projects.
|
||||
</p>
|
||||
)}
|
||||
{archivedProjects.map((project) => (
|
||||
<div
|
||||
key={project.id}
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-2"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
className="inline-block w-2 h-2 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: project.color }}
|
||||
/>
|
||||
<span className="text-sm truncate">
|
||||
{project.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRestore(project.id)}
|
||||
className="px-2 py-1 text-xs font-medium rounded-md border border-border hover:bg-background-tertiary transition-colors"
|
||||
>
|
||||
Restore
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeletingProject(project)}
|
||||
className="px-2 py-1 text-xs font-medium rounded-md text-foreground-danger hover:bg-background-danger/10 transition-colors"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeSection === "about" && (
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">About</h3>
|
||||
<p className="mt-1 text-sm text-foreground-secondary">
|
||||
About information will appear here.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { Message } from "@/shared/types/messages";
|
||||
import type { Session } from "@/shared/types/agents";
|
||||
import type { Session } from "@/shared/types/chat";
|
||||
|
||||
export async function createSession(agentId?: string): Promise<Session> {
|
||||
return invoke("create_session", { agentId });
|
||||
|
||||
@@ -79,47 +79,4 @@ export interface CreateAgentRequest {
|
||||
acpEndpoint?: string;
|
||||
}
|
||||
|
||||
// Session types
|
||||
export interface Session {
|
||||
id: string;
|
||||
title: string;
|
||||
agentId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messageCount: number;
|
||||
lastMessagePreview?: string;
|
||||
}
|
||||
|
||||
// Token tracking
|
||||
export interface TokenState {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
totalTokens: number;
|
||||
accumulatedInput: number;
|
||||
accumulatedOutput: number;
|
||||
accumulatedTotal: number;
|
||||
}
|
||||
|
||||
// Chat state
|
||||
export type ChatState =
|
||||
| "idle"
|
||||
| "thinking"
|
||||
| "streaming"
|
||||
| "waiting"
|
||||
| "compacting"
|
||||
| "error";
|
||||
|
||||
// SSE event types (from goosed server)
|
||||
export type MessageEventType =
|
||||
| "message"
|
||||
| "error"
|
||||
| "finish"
|
||||
| "modelChange"
|
||||
| "notification"
|
||||
| "updateConversation"
|
||||
| "ping";
|
||||
|
||||
export interface MessageEvent {
|
||||
type: MessageEventType;
|
||||
data: unknown;
|
||||
}
|
||||
// Session, TokenState, ChatState, and MessageEventType are defined in ./chat.ts
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./messages";
|
||||
export * from "./agents";
|
||||
export * from "./chat";
|
||||
|
||||
Reference in New Issue
Block a user