9.5 KiB
AGENTS.md
This file contains guidelines for agentic coding assistants working on the Aventuras codebase.
Build/Lint/Test Commands
Development & Build
npm run dev # Start dev server (Vite)
npm run build # Build for production
npm run preview # Preview production build
npm run tauri dev # Start Tauri development window
Type Checking
npm run check # Run svelte-check (type checking)
npm run check:watch # Watch mode type checking
npx svelte-check --tsconfig ./tsconfig.json # Direct type check
Testing
Current Status: No test suite is currently configured in package.json.
If/when tests are added (e.g., using Vitest), the standard commands would be:
npm test # Run all tests
npm test -- -t "test name" # Run a single test (if using Vitest/Jest)
Project Architecture
- Framework: SvelteKit 2 + Tauri 2 (desktop app)
- Language: TypeScript (strict mode)
- Styling: Tailwind CSS
- State Management: Svelte 5 runes (
$state,$derived,$effect,$props) - Database: SQLite via
@tauri-apps/plugin-sql - AI: OpenAI-compatible APIs (OpenRouter, custom providers)
UI Design & Component System
Component Library
- Library: shadcn-svelte
- Location:
src/lib/components/ui/ - Installation: Use
npx shadcn-svelte@latest add [component]to add new components. - Icons: Lucide Svelte (
lucide-svelte)
Theming System
The application uses a sophisticated CSS variable-based theming system defined in src/app.css. It supports multiple distinct visual themes that override standard Tailwind/Shadcn tokens.
Available Themes:
- Default (Dark): Modern slate/blue dark mode.
- Light (Paper): Warm, high-contrast, paper-like aesthetic.
- Light (Solarized): Classic solarized light palette.
- Retro Console: CRT terminal aesthetic (green/amber on black) with scanline effects.
- Fallen Down: Undertale/Deltarune inspired high-contrast pixel art aesthetic (black/white/yellow).
CSS Variables & Tokens
- Shadcn Tokens: Standard tokens (
--background,--foreground,--primary,--muted, etc.) are mapped to theme-specific colors inapp.css. - Surface System: Custom
surface-*(50-950) andaccent-*(50-950) scales are used for fine-grained control across themes. - Typography:
- UI Font: System sans-serif.
- Story Text: Configurable via
--font-story(Serif for default, Monospace for Retro/Fallen Down).
Usage Guidelines
- Prefer Shadcn Components: Use components from
$lib/components/uiwhenever possible (e.g.,Button,Input,Card). - Tailwind Classes: Use standard Tailwind classes. They will automatically adapt to the active theme via the CSS variables.
- Custom Styling: If custom CSS is needed, use the CSS variables defined in
app.cssto ensure theme compatibility (e.g.,var(--bg-secondary)instead of hardcoded hex). - Icons: Import icons from
lucide-svelte(e.g.,import { Save } from 'lucide-svelte';).
Current Refactor: Preset-Based Service Configuration
Status: In Progress - Phase 3 (AgentProfiles UI) Complete
Overview
Implementing preset-based service configuration to fix bugs where services have presetId fields but don't actually use them at runtime. This addresses the issue where Story Wizard categories don't populate in Agent Profiles UI at first.
Completed (Phase 1: Service Updates)
Services updated to accept presetId and use settings.getPresetConfig(presetId):
- MemoryService ✅
- ClassifierService ✅
- SuggestionsService ✅
- ActionChoicesService ✅
- StyleReviewerService ✅
- TimelineFillService ✅
- LoreManagementService ✅
- AgenticRetrievalService ✅
- EntryRetrievalService ✅
- ImageGenerationService ✅
- InteractiveLorebookService ✅
Completed (Phase 2: Service-Specific Settings)
- Added
serviceSpecificSettingsstate property to SettingsStore - Implemented default functions for all 15 service-specific interfaces:
- ClassifierSpecificSettings
- LorebookClassifierSpecificSettings
- MemorySpecificSettings
- SuggestionsSpecificSettings
- ActionChoicesSpecificSettings
- StyleReviewerSpecificSettings
- LoreManagementSpecificSettings
- InteractiveLorebookSpecificSettings
- AgenticRetrievalSpecificSettings
- TimelineFillSpecificSettings
- ChapterQuerySpecificSettings
- EntryRetrievalSpecificSettings
- ImageGenerationSpecificSettings
- TTSSpecificSettings
- CharacterCardImportSpecificSettings
- Added database loading/saving for
serviceSpecificSettings - Added helper methods:
saveServiceSpecificSettings(),resetServiceSpecificSettings()
Completed (Phase 3: AgentProfiles UI)
- Updated
getServiceSettings()to usesettings.servicePresetAssignmentsinstead ofsystemServicesSettings/wizardSettings - Updated
getServicesForProfile()to usesettings.servicePresetAssignments - Updated
handleSavePreset()to only save the preset (no longer propagates to services) - Updated
handleDeletePreset()to reset assignments usingsettings.setServicePresetId() - Updated
handleAssignPreset()to usesettings.setServicePresetId() - Updated
handleResetProfiles()to resetsettings.servicePresetAssignmentsto defaults - Added
imageGenerationtosystemServiceslist anddefaultAssignments
Remaining Work
Phase 4: Add migration logic for existing users Phase 5: Testing and verification
Key Architecture Changes
- Services now accept
presetIdparameter in constructor - Services use
settings.getPresetConfig(presetId)for generation config - Service-specific fields (like
chatHistoryTruncation) remain separate - Default preset assignments in
servicePresetAssignmentsstate
Notes
- TTSService is a separate audio generation service (not LLM text generation), so it doesn't use generation presets
- CharacterCardImport service doesn't exist (only defined in settings interfaces)
Code Style Guidelines
File Organization
src/
├── routes/ # SvelteKit pages (+page.svelte, +layout.svelte)
├── lib/
│ ├── components/ # Svelte components (PascalCase.svelte)
│ ├── services/ # Business logic classes (PascalCase.ts) or modules (camelCase.ts)
│ ├── stores/ # Svelte stores (*.svelte.ts for runes)
│ ├── types/ # TypeScript types (index.ts)
│ └── utils/ # Utility functions (camelCase.ts)
Naming Conventions
- Components:
PascalCase.svelte(e.g.,StoryView.svelte) - Services:
PascalCase.ts(classes) orcamelCase.ts(modules) - Functions/Vars:
camelCase(e.g.,generateResponse,isLoading) - Types:
PascalCase(e.g.,interface Story) - Constants:
UPPER_SNAKE_CASE(e.g.,DEFAULT_CONFIG) - Handlers:
handle<Action>(e.g.,handleSubmit)
Imports
- Use
$libalias. - Use
typekeyword for type-only imports.
import type { Story } from '$lib/types';
import { settings } from '$lib/stores/settings.svelte';
Svelte Component Patterns (Svelte 5)
Use Runes ($state, $derived, $props). Avoid export let.
<script lang="ts">
import { story } from '$lib/stores/story.svelte';
interface Props {
entry: StoryEntry;
onAction?: () => void;
}
let { entry, onAction }: Props = $props();
let isOpen = $state(false);
let isActive = $derived(entry.status === 'active');
$effect(() => {
// Side effects here
return () => { /* cleanup */ };
});
function handleClick() {
isOpen = !isOpen;
}
</script>
<button onclick={handleClick} class="btn btn-primary">
{entry.content}
</button>
TypeScript Patterns
- Strict Mode: No
any. - Async/Await: Always handle errors with
try/catchin top-level or service methods. - Null Safety: Use
?.and??.
async function loadStory(id: string): Promise<void> {
try {
const story = await database.getStory(id);
if (!story) throw new Error(`Story not found: ${id}`);
this.currentStory = story;
} catch (error) {
console.error('[StoryStore] Failed to load:', error);
throw error;
}
}
State Management (Runes)
Use .svelte.ts files for global state.
class StoryStore {
currentStory = $state<Story | null>(null);
entries = $state<StoryEntry[]>([]);
// Computed
get activeCharacters() {
return this.characters.filter(c => c.status === 'active');
}
// Action
async addEntry(content: string) {
const entry = await database.addEntry({ content });
this.entries = [...this.entries, entry]; // Immutable update
}
}
export const story = new StoryStore();
Tailwind CSS
- Use utility classes directly.
- Dark mode:
surface-*colors. - Responsive:
sm:,md:,lg:. - Layout:
flexorgrid.
<div class="flex h-full flex-col bg-surface-900 p-4 sm:p-6">
<!-- Content -->
</div>
AI Integration
- Use abstract providers in
src/lib/services/ai. - Handle streaming with async generators.
async *streamResponse(context: Context): AsyncIterable<StreamChunk> {
const provider = this.getProvider();
for await (const chunk of provider.streamResponse(context)) {
yield chunk;
}
}
Database
- Use
src/lib/services/database.ts. - Parameterize all SQL queries.
When in Doubt
- Search: Use
grep/globto find existing patterns. - Type Check: Run
npm run check. - Consistency: Mimic surrounding code.
- Safety: No secrets in code.