Update AGENTS.md

This commit is contained in:
NeilClack
2026-07-16 20:23:59 -05:00
parent 2d0cf98ed3
commit f5896aa575
5 changed files with 1351 additions and 284 deletions
+235
View File
@@ -0,0 +1,235 @@
# API Documentation
Only read this file when modifying API routes, authentication, keys, or integrations.
---
# API Location
API routes:
src/routes/api/v1/
---
# Authentication
Most API endpoints require:
Authorization: Bearer <API key>
API keys are:
- generated randomly
- hashed with SHA-256 before storage
- displayed using stored prefix only
Never store plaintext API keys.
---
# API Key Lifecycle
Normal rotation:
- New key generated.
- Old key remains valid for 48 hours.
- Old key expires using expiresAt.
Emergency rotation:
- Old key immediately invalidated.
- revokedAt is set.
---
# API Routes
## Categories
GET:
/api/v1/categories
Requires API key.
---
## Frequencies
GET:
/api/v1/frequencies
Requires API key.
---
## Proclamations
GET:
/api/v1/proclamations
Supports:
- optional key filtering
- optional email filtering
Requires API key.
---
## License Validation
POST:
/api/v1/license
Payload:
{
"license_key": "...",
"email_address": "..."
}
Requires API key.
---
# GHL Account Creation Webhook
Endpoint:
POST /api/v1/automations/create/account
This endpoint does NOT require an API key.
It is used by GoHighLevel automation.
---
# Account Creation Flow
When a request arrives:
1. Check whether the email already exists.
If user does not exist:
2. Create user through Better Auth.
3. Generate temporary password.
4. Retrieve verification token.
5. Build verification URL.
6. Send registration email.
Then:
7. Create LicenseKey.
8. Send license key delivery email.
If user already exists:
1. Skip signup.
2. Create license key.
3. Send delivery email.
---
# Email Templates
Controlled by:
src/lib/server/resend.ts
Templates:
automated-user-registration
Variables:
- firstName
- tempPassword
- verifyUrl
- loginUrl
license-key-delivery
Variables:
- firstName
- license_key
email-verification-email
Variables:
- target_email_address
- verification_link
---
# Better Auth
Server:
src/lib/auth.ts
Client:
src/lib/auth-client.ts
Email callbacks:
- sendVerificationEmail
- sendResetPassword
Failures should:
- log errors
- not crash requests
Use:
void promise.catch()
pattern.
---
# Security Rules
Never:
- expose plaintext API keys
- bypass authorization checks
- trust client role information
- skip validation
Always:
- validate input
- check permissions
- preserve existing security patterns
---
# API Change Workflow
Before modifying an endpoint:
1. Read the route.
2. Read related auth middleware.
3. Read database models involved.
4. Check existing tests.
After modifying:
Run:
npm run check
npm run test
Fix failures before completing.
+184
View File
@@ -0,0 +1,184 @@
# Svelte 5 Patterns and Rules
This document contains Svelte-specific implementation rules.
Only read this file when modifying Svelte components, routes, or Svelte-specific logic.
---
# Svelte Version
This project uses:
- SvelteKit 2
- Svelte 5
- runes mode
Always use:
- $state()
- $derived()
- $effect()
- $props()
Do not use legacy stores:
- writable()
- readable()
- derived()
---
# Component Props
Always destructure $props() with defaults.
Preferred pattern:
let {
record = {} as Record<string, unknown>,
fieldErrors = {} as Record<string, string>,
isSuper = false
}: {
record?: Record<string, unknown>;
fieldErrors?: Record<string, string>;
isSuper?: boolean;
} = $props();
Do not assume props exist.
---
# State Synchronization
IMPORTANT:
Do not initialize $state from props when the prop can change.
Incorrect:
let name = $state(record.name);
This only captures the initial value.
Correct:
let name = $state('');
$effect(() => {
name = String(record.name ?? '');
});
Use $effect whenever component state needs to follow changing props.
---
# Effect Ordering
$effect blocks execute in declaration order.
When multiple effects modify related state:
1. Reset state first.
2. Populate state second.
3. Perform dependent calculations afterward.
Example:
let dirty = $state(false);
$effect(() => {
dirty = false;
});
$effect(() => {
name = String(record.name ?? '');
});
This ensures a new record starts clean.
---
# Select Inputs
IMPORTANT:
Do not use bind:value on select elements whose values are synchronized through $effect.
Incorrect:
<select bind:value={frequency}>
<option value="1">One</option>
</select>
This can fail to update correctly.
Use:
<select onchange={(e) => frequency = e.currentTarget.value}>
<option value="1" selected={frequency === '1'}>
One
</option>
</select>
Use selected on options.
---
# Input Elements
Text inputs are different.
This is acceptable:
<input bind:value={name} />
bind:value works correctly with normal input elements.
---
# Forms
Admin forms use:
- $state for field values
- $effect for prop synchronization
- oninput for dirty tracking
- use:enhance for submission
Example:
<form oninput={markDirty}>
<button disabled={!!record?.id && !dirty}>
Save
</button>
</form>
---
# Avoid Unnecessary State
Prefer:
$derived()
for values computed from other state.
Use:
$state()
only for values that change independently.
---
# Component Changes
Before modifying a component:
1. Read the entire component.
2. Find similar components.
3. Follow existing patterns.
4. Avoid introducing new abstractions unless necessary.
Small, consistent changes are preferred.
+241
View File
@@ -0,0 +1,241 @@
# Testing Patterns
Only read this file when creating or modifying tests.
This project uses:
- Vitest
- Playwright browser testing
- Svelte Testing Library patterns through vitest/browser
---
# Test Philosophy
Tests should prove behavior.
Do not write tests that only confirm implementation details.
Every test must contain meaningful assertions.
A test without assertions fails because:
expect.requireAssertions is enabled.
---
# Test Locations
Tests are colocated with source files.
Client tests:
*.svelte.test.ts
*.svelte.spec.ts
Server tests:
*.test.ts
*.spec.ts
---
# Vitest Projects
There are two test environments.
## Client
Used for Svelte components.
Environment:
Playwright Chromium browser.
Used for:
- rendering
- user interaction
- form behavior
---
## Server
Used for:
- load functions
- actions
- server utilities
Environment:
Node.
---
# Before Each Test
Always reset mocks:
beforeEach(() => {
vi.clearAllMocks();
});
---
# Server Tests
Server route tests should cover:
- authenticated behavior
- unauthenticated behavior
- authorization rules
- successful operations
- failed operations
Mock:
- $lib/db
- authentication helpers
- external services
---
# Prisma Mocking
Mock only the methods used.
Example:
vi.mock('$lib/db', () => ({
prisma: {
proclamation: {
findMany: vi.fn(),
findUnique: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn()
}
}
}));
Do not create massive unused mocks.
---
# Authentication Testing
Use:
createLocals()
Examples:
Unauthenticated:
createLocals()
Authenticated:
createLocals(mockUser() as any)
Specific roles:
createLocals(
mockUser({ roleId: 4 }) as any
)
---
# Client Test Locators
Available:
getByRole
getByText
getByPlaceholder
getByAltText
getByTestId
Unavailable:
getByLabel
getByDisplayValue
---
# Role Queries
Always use exact regex names.
Preferred:
page.getByRole(
'textbox',
{ name: /^Name$/ }
)
Avoid:
page.getByRole(
'textbox',
{ name: 'Name' }
)
because substring matching can create strict-mode failures.
---
# Select Testing
Because selects use selected options instead of bind:value:
const element =
await page
.getByRole(
'combobox',
{ name: /^Frequency$/ }
)
.element();
expect(
(element as HTMLSelectElement).value
).toBe('2');
---
# Svelte Component Mocking
When mocking Svelte 5 components:
Use:
vi.mock('$lib/components/example.svelte', () => ({
default: ($anchor: Element) => {
const div = document.createElement('div');
$anchor.replaceWith(div);
}
}));
Imports using mocked modules must come after vi.mock().
If variables are needed inside mocks:
Use vi.hoisted().
---
# Test Completion Rule
After writing tests:
Run:
npm run test
If failures occur:
Fix them.
Do not report "tests added" without verifying they pass.