- TODO__Agents.md: mark IDAA IDB caching item complete (audited 2026-04-28); all protection layers confirmed in place, no code changes needed - GUIDE__SvelteKit2_Svelte5_DexieJS.md: add "SvelteKit Layout Hierarchy: Security and Execution Order" section explaining execution order, auth-gate consequences, pre-gate risks in +page.ts/+layout.ts, and the reactivity-guard vs auth-guard distinction for IDAA $effect blocks - BOOTSTRAP__AI_Agent_Quickstart.md: add Mistake #7 — treating $effect blocks as auth bypass risks vs understanding the real layout hierarchy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 KiB
Aether SvelteKit — AI Agent Bootstrap / Quickstart
Read this first. This doc is the fast path to being productive on this project. It covers the rules, patterns, and gotchas that matter most. Deep dives are in the linked docs at the bottom.
1. What This Project Is
Aether is an event management platform built by One Sky IT (Scott Idem). This repo is the frontend: Svelte 5 (runes mode) + SvelteKit v2.
The backend is a separate repo (aether_api_fastapi) — a FastAPI + MariaDB app
running in Docker. The frontend talks to it exclusively via the V3 REST API.
Key clients:
- Conference organizers — Presentation Management (pres_mgmt), Launcher, Badges
- Exhibitors — Leads capture
- IDAA — International Doctors in Alcoholics Anonymous (private medical/recovery community)
Stack at a glance:
| Layer | Technology |
|---|---|
| Framework | Svelte 5 (runes mode) + SvelteKit v2 |
| Styling | Tailwind CSS v4 + Flowbite (Skeleton UI being phased out) |
| State | $state/$derived runes + Dexie.js IndexedDB (liveQuery) |
| Icons | Lucide (@lucide/svelte) |
| Editors | CodeMirror 6 (primary), Edra/TipTap (secondary) |
| Native | Electron app for onsite launcher (src/lib/electron/electron_relay.ts) |
| Backend | FastAPI + MariaDB, V3 API (/v3/crud/, /v3/lookup/) |
| Auth | Custom headers: x-aether-api-key + x-account-id (no Bearer tokens) |
2. Critical Rules — Read Before Touching Any Code
Privacy (Sev-1 class failures if violated)
- IDAA content is ALWAYS private. All routes under
/idaa/require authentication. A previous AI agent accidentally made IDAA bulletin board data publicly accessible. This is the single most serious class of mistake on this project. When in doubt — it's private. - Journals are private personal data. Always authenticated.
File Safety
- Never use
rmto delete files. Move to~/tmp/agents_trashinstead. - Never commit
.envfiles, API keys, or passwords.
Before Every Commit
- Run
npx svelte-check— zero errors, zero warnings. No exceptions. - Atomic commits: one component or one fix per commit.
Before Starting Any Task
- Read
documentation/TODO__Agents.md— it has active tasks, known bugs, and context about what was recently changed and why.
V3 API — Never Include the Object ID in PATCH Body Fields
The ID is in the URL. Including it in data_kv causes a 400: Unknown column in SET.
// WRONG — causes 400 error:
update_ae_obj__event_file({ event_file_id, data_kv: { event_file_id, file_purpose: 'final' } })
// CORRECT:
update_ae_obj__event_file({ event_file_id, data_kv: { file_purpose: 'final' } })
3. Environment & Deploy Cheat Sheet
There are two separate .env systems — do not confuse them:
| System | File | Controls |
|---|---|---|
aether_container_env/.env |
Docker orchestration | Ports, AE_CFG_ID, replicas, paths |
aether_app_sveltekit/.env.* |
Vite/SvelteKit build | PUBLIC_* API vars baked into the JS bundle |
The 4 commands you run and which env file each uses:
| Command | Env file read |
|---|---|
npm run dev |
aether_app_sveltekit/.env.local (Vite dev server, localhost:5173) |
npm run build:docker:dev |
aether_app_sveltekit/.env.dev (baked into local Docker image) |
npm run deploy:remote:test |
/srv/apps/test_aether_app_sveltekit/.env.test on Linode |
npm run deploy:remote:prod |
/srv/apps/prod_aether_app_sveltekit/.env.prod on Linode |
The .env.* files are gitignored (only .default templates are tracked). They must be
placed manually on each server during initial setup. On the workstation you only need
.env.local and .env.dev. The Linode servers each have exactly one env file for their environment.
What goes in every SvelteKit env file (same 8 vars, different values per env):
PUBLIC_AE_API_PROTOCOL=https
PUBLIC_AE_API_SERVER=<api server hostname>
PUBLIC_AE_API_BAK_SERVER=<bak api hostname>
PUBLIC_AE_API_PORT=443
PUBLIC_AE_API_PATH=
PUBLIC_AE_API_SECRET_KEY=<key>
PUBLIC_AE_CRUD_SUPER_KEY=<key>
PUBLIC_AE_BOOTSTRAP_KEY=<key>
PUBLIC_AE_NO_ACCOUNT_ID=No_Account_ID_Here
4. Svelte 5 Runes Mode — Key Patterns & Gotchas
This codebase is fully Svelte 5 runes mode. No Svelte 4 syntax.
The basics
<script lang="ts">
// Props — with optional two-way binding
interface Props { count?: number; label: string; }
let { count = $bindable(0), label }: Props = $props();
// Reactive state
let value = $state('');
let upper = $derived(value.toUpperCase());
// Side effects (replaces onMount + $: reactive)
$effect(() => {
console.log('value changed:', value);
return () => { /* cleanup */ };
});
</script>
What NOT to use (Svelte 4 patterns — do not introduce)
// ❌ No writable() stores for component state
import { writable } from 'svelte/store';
// ❌ No reactive declarations
$: doubled = count * 2;
// ❌ No onDestroy for cleanup — use $effect return instead
onDestroy(() => cleanup());
$bindable() vs $state()
- Use
$bindable()when the parent needs two-way binding on a prop. - Use
$state()for local component state with no external binding.
Store reactivity trap (important for $effect)
The app uses svelte-persisted-store (Svelte 4 contract) for $ae_loc, $ae_api,
$ae_sess, etc. In Svelte 5 $effect, reading any field of a Svelte 4 store
subscribes to the entire store. This means unrelated writes to $ae_loc
(e.g. iframe height, SWR reload) will re-trigger your effect. Be conservative about
what you read from these stores inside $effect blocks. See PROJECT__Stores_Svelte5_Migration.md
for the long-term fix plan.
{#await} blocks
{#await somePromise}
<LoadingSpinner />
{:then result}
<div>{result}</div>
{:catch error}
<ErrorMessage {error} />
{/await}
5. V3 API Patterns
SWR (Stale-While-Revalidate) — the standard load pattern
Return cached Dexie data immediately, refresh from API in background.
async function load_ae_obj_id__my_obj({ api_cfg, obj_id }) {
// 1. Return stale cache immediately (fast)
const cached = await db.my_obj.get(obj_id);
if (cached) my_obj_state = cached;
// 2. Fetch fresh from API in background
_refresh_my_obj_background({ api_cfg, obj_id });
}
ID convention — never use _id_random fields
The V3 API uses random string IDs (e.g. event_file_id = "aBc123"). The *_id_random
fields are legacy aliases. Always use the short form:
// ✅ Correct
event_file_obj.event_file_id
// ❌ Wrong — legacy alias, don't use
event_file_obj.event_file_id_random
PATCH — only field values in the body
// The obj_id goes in the URL (handled by update_ae_obj__* function).
// Only the fields you want to update go in data_kv.
await events_func.update_ae_obj__event_file({
api_cfg: $ae_api,
event_file_id: 'aBc123', // → becomes the URL path param
data_kv: { file_purpose: 'final' } // → only changed fields
});
Auth headers (set automatically by api.ts)
x-aether-api-key: <PUBLIC_AE_API_SECRET_KEY>
x-account-id: <account_id>
6. Naming Conventions
| Pattern | Example | Used for |
|---|---|---|
ae_comp__* |
ae_comp__event_badge.svelte |
Route-level components |
ae_<module>_comp__* |
ae_events_comp__session_list.svelte |
Module-scoped components |
element_* |
element_input_files_tbl.svelte |
Reusable library primitives |
lq__* |
lq__journal_obj |
Read-only liveQuery |
lqw__* |
lqw__journal_obj |
Writable form snapshot liveQuery |
ae_<module>__<obj>.ts |
ae_journals__journal.ts |
Object type + functions |
db_<module>.ts |
db_journals.ts |
Dexie instance per module |
The canonical pattern reference is the Journals module (src/lib/ae_journals/).
When building anything new, model it after Journals.
7. Mistakes Agents Have Made on This Project
These are real incidents — know them before you start.
-
IDAA BB exposed publicly — an agent removed an auth guard from the bulletin board route. All IDAA content must be behind authentication. Always check route guards when touching
/idaa/routes. -
event_file_idin PATCH body (400 error) — including the object ID indata_kvwhen callingupdate_ae_obj__*. The V3 API tries toSET event_file_id = ...which fails because it's a view alias, not a DB column. See Section 2 above. -
Bad
.d.tsdeclaration silently hid 1368 errors — adeclare moduleinapp.d.ts(a script-context file) replaced the entire@lucide/sveltetype exports instead of merging.svelte-checkshowed 0 errors, masking real problems. Ifsvelte-checksuddenly drops to 0 errors, verify it's not because a bad declaration wiped a module. -
Coarse store reactivity loop — an
$effectthat read$ae_loc.some_fieldwas re-triggering repeatedly because unrelated writes to$ae_loc(e.g. SWR config reload) fired the effect. In Svelte 5, any read of a Svelte 4 store inside$effectsubscribes to the whole store. Scope what you read carefully. -
file_purpose == 'admin'not hidden in Launcher — thehide_draftprop hidoutlineanddraftfiles but notadminfiles. Gaps like this happen when a new enum value is added to a field without auditing all the places that filter on it. -
Deleting files with
rm— always move to~/tmp/agents_trash. A deleted file may contain context that's not recoverable from git if it was gitignored. -
Treating
$effectblocks as auth bypass risks — a$effectinside a child component cannot bypass a parent+layout.svelteauth gate. Children only mount if the parent calls{@render children?.()}. Adding redundant auth guards to$effectblocks that can only run after the parent gate already passed is unnecessary — and misleads future readers into thinking the parent gate is not sufficient on its own. The real pre-gate risk is+page.ts/+layout.ts: universal load functions run before any layout mounts and also fire during SvelteKit link prefetch. Keep those files clean of data loads in private modules. SeeGUIDE__SvelteKit2_Svelte5_DexieJS.md→ "SvelteKit Layout Hierarchy: Security and Execution Order" for the full explanation.
8. Source Layout (Quick Reference)
src/lib/
ae_api/ — API helpers (V3 preferred)
ae_core/ — Account, User, Person, Site, hosted files
ae_events/ — Events, sessions, presenters, badges, locations, files
ae_journals/ — Journals (canonical/frontier model — copy patterns from here)
ae_idaa/ — IDAA custom module (PRIVATE — always authenticated)
elements/ — Reusable UI: V3 field editor, data store, CodeMirror, QR scanner
electron/ — Native Electron bridge (electron_relay.ts)
stores/ — ae_stores.ts, ae_events_stores.ts, ae_idaa_stores.ts
src/routes/
/core/ — Admin (accounts, people, sites, users)
/events/[id]/
/(pres_mgmt)/ — Presentation management
/(launcher)/ — Event launcher (kiosk display)
/(badges)/ — Badge printing
/(leads)/ — Exhibitor leads
/journals/ — Journals
/idaa/ — IDAA module (PRIVATE)
/hosted_files/ — File management
9. Reading Order for Deeper Dives
Start here, then go deeper as needed:
| What you need | Read |
|---|---|
| Active tasks + known bugs | documentation/TODO__Agents.md ← always first |
| Dev workflow + commit rules | documentation/GUIDE__Development.md |
| V3 API reference | documentation/GUIDE__AE_API_V3_for_Frontend.md |
| Dexie / liveQuery patterns | documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md |
| Svelte 5 patterns + pitfalls | documentation/GEMINI__Svelte_and_Me.md |
| Permissions + auth levels | documentation/AE__Permissions_and_Security.md |
| Electron / native launcher | documentation/PROJECT__AE_Events_Launcher_Native_integration.md |
| Store migration plan | documentation/PROJECT__Stores_Svelte5_Migration.md |
| Exhibitor Leads module | documentation/MODULE__AE_Events_Exhibitor_Leads.md |
| Naming conventions | documentation/AE__Naming_Conventions.md |