178 lines
4.6 KiB
Markdown
178 lines
4.6 KiB
Markdown
# Claude Code Guidelines
|
|
|
|
## Project Overview
|
|
|
|
This is a SvelteKit 2.x application using Svelte 5 with Tailwind CSS v4.
|
|
|
|
## Code Style Rules
|
|
|
|
### SVG Attributes
|
|
|
|
When using SVG elements, use `style` attribute instead of `fill` attribute to avoid IDE obsolete attribute warnings:
|
|
|
|
```svelte
|
|
<!-- Correct -->
|
|
<svg style="fill: currentColor" ...>
|
|
<svg style="fill: none" ...>
|
|
|
|
<!-- Incorrect (triggers obsolete attribute warning) -->
|
|
<svg fill="currentColor" ...>
|
|
<svg fill="none" ...>
|
|
```
|
|
|
|
### Svelte 5 Runes
|
|
|
|
- Use `$state()` for reactive state
|
|
- Use `$derived()` for computed values
|
|
- Use `$props()` for component props
|
|
- Use `$effect()` for side effects
|
|
|
|
### Page Store
|
|
|
|
Use `$app/state` instead of `$app/stores` for the page object:
|
|
|
|
```svelte
|
|
<!-- Correct -->
|
|
import {page} from '$app/state'; const path = page.url.pathname;
|
|
|
|
<!-- Incorrect -->
|
|
import {page} from '$app/stores'; const path = $page.url.pathname;
|
|
```
|
|
|
|
### Theme Classes
|
|
|
|
Use the custom theme utility classes defined in `layout.css`:
|
|
|
|
- `text-theme`, `text-theme-secondary`, `text-theme-muted`
|
|
- `bg-theme`, `bg-theme-card`
|
|
- `border-theme`
|
|
- `shadow-theme`, `shadow-theme-lg`
|
|
|
|
### Hover/Active States
|
|
|
|
Use consistent hover/active patterns:
|
|
|
|
```
|
|
hover:bg-black/5 dark:hover:bg-white/10
|
|
active:bg-black/10 dark:active:bg-white/15
|
|
```
|
|
|
|
### Mobile Grid Layouts
|
|
|
|
For card grids that should display single-column on mobile and multi-column on larger screens, always:
|
|
|
|
1. Explicitly set `grid-cols-1` (don't rely on CSS grid's implicit single column)
|
|
2. Add `min-w-0` on grid items with flex content to allow proper shrinking
|
|
|
|
```svelte
|
|
<!-- Correct -->
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div class="card-example flex min-w-0 items-center justify-between">
|
|
<div class="min-w-0 flex-1">...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Incorrect (may overflow on mobile) -->
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<div class="card-example flex items-center justify-between">...</div>
|
|
</div>
|
|
```
|
|
|
|
### Button Element Content
|
|
|
|
Button elements can only contain phrasing content (inline elements). Use `<span>` instead of `<div>`, `<p>`, or `<h1>`-`<h6>` inside buttons:
|
|
|
|
```svelte
|
|
<!-- Correct -->
|
|
<button>
|
|
<span class="flex items-center gap-2">
|
|
<span>Click me</span>
|
|
</span>
|
|
</button>
|
|
|
|
<!-- Incorrect (triggers HTML validation warning) -->
|
|
<button>
|
|
<div class="flex items-center gap-2">
|
|
<p>Click me</p>
|
|
</div>
|
|
</button>
|
|
```
|
|
|
|
For multi-line button content, use `<span class="block">` to create line breaks:
|
|
|
|
```svelte
|
|
<button>
|
|
<span class="flex items-center gap-2">Title</span>
|
|
<span class="text-muted block text-sm">Subtitle on new line</span>
|
|
</button>
|
|
```
|
|
|
|
### SvelteSet Reactivity
|
|
|
|
`SvelteSet` from `svelte/reactivity` is already reactive and does NOT need to be wrapped in `$state()`:
|
|
|
|
```svelte
|
|
<!-- Correct -->
|
|
import { SvelteSet } from 'svelte/reactivity';
|
|
let expandedItems = new SvelteSet<string>();
|
|
|
|
<!-- Incorrect (unnecessary wrapper) -->
|
|
let expandedItems = $state(new SvelteSet<string>());
|
|
```
|
|
|
|
### Each Block Keys
|
|
|
|
Always provide a key for `{#each}` blocks to avoid ESLint warnings:
|
|
|
|
```svelte
|
|
<!-- Correct -->
|
|
{#each items as item (item.id)}
|
|
{#each days as day (day)}
|
|
|
|
<!-- Incorrect -->
|
|
{#each items as item}
|
|
```
|
|
|
|
### Date Formatting
|
|
|
|
Use the date utilities from `$lib/utils/date` which use `date-fns` to properly handle ISO date strings without timezone offset issues:
|
|
|
|
```svelte
|
|
import {formatDate} from '$lib/utils/date';
|
|
|
|
<!-- Returns empty string for null/undefined, so add fallback -->
|
|
{formatDate(startDate) || '—'}
|
|
```
|
|
|
|
### File Organization
|
|
|
|
- Place `.svelte.ts` files (stores, shared state) in `src/lib/stores/`, not in component folders
|
|
- GraphQL mutations go in `src/lib/graphql/mutations/{entity}/`
|
|
- GraphQL queries go in `src/lib/graphql/queries/{entity}/`
|
|
|
|
### Backend Integration Notes
|
|
|
|
- The backend uses a unique constraint allowing only ONE active scope per address
|
|
- The `isConditional` field on tasks is legacy and should always be set to `false`
|
|
- GraphQL inputs use camelCase (e.g., `isActive`, `accountId`) which maps to snake_case in Django
|
|
|
|
### Task Frequency Values
|
|
|
|
The backend `TaskFrequencyChoices` enum uses **lowercase** values. Handle frequency differently depending on the operation:
|
|
|
|
**Individual mutations** (create/update task or task template) - use lowercase:
|
|
|
|
```typescript
|
|
frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'triannual' | 'annual' | 'as_needed';
|
|
```
|
|
|
|
**JSON import** (`createScopeTemplateFromJson`) - uppercase is acceptable:
|
|
|
|
```json
|
|
{ "frequency": "DAILY" }
|
|
```
|
|
|
|
The backend's `build_scope_template` service normalizes uppercase to lowercase automatically.
|
|
|
|
Valid frequency values: `daily`, `weekly`, `monthly`, `quarterly`, `triannual`, `annual`, `as_needed`
|