# Aether SvelteKit — AI Agent Bootstrap / Quickstart > **Doc Owner:** Frontend platform maintainers (OSIT) + active coding agents > **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 only: `x-aether-api-key` + `x-account-id` — **NOT** 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 `rm`** to delete files. Move to `~/tmp/agents_trash` instead. - Never commit `.env` files, 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`. ```ts // 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): ```env PUBLIC_AE_API_PROTOCOL=https PUBLIC_AE_API_SERVER= PUBLIC_AE_API_BAK_SERVER= PUBLIC_AE_API_PORT=443 PUBLIC_AE_API_PATH= PUBLIC_AE_API_SECRET_KEY= PUBLIC_AE_CRUD_SUPER_KEY= PUBLIC_AE_BOOTSTRAP_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 ```svelte ``` ### What NOT to use (Svelte 4 patterns — do not introduce) ```ts // ❌ 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 has two kinds of persisted stores — know which you're reading: **Svelte 4 `svelte-persisted-store` (coarse reactivity) — still used for:** - `$ae_loc`, `$ae_sess`, `$ae_api` (global app state) - `$idaa_loc`, `$idaa_sess` (IDAA module) In Svelte 5 `$effect`, reading **any field** of these stores subscribes to the **entire store**. 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. **Svelte 5 `PersistedState` (fine-grained reactivity) — Events module stores:** - `badges_loc`, `leads_loc`, `pres_mgmt_loc`, `launcher_loc`, `events_auth_loc` These use `runed`'s `PersistedState`. Access via `.current` (no `$` sigil): `badges_loc.current.field`. Writing one field only re-triggers effects that read that field. Import from the `.svelte` extension: `import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'`. For search pages using the coarse stores, this usually means: - keep true user preferences in persisted local state - keep transient triggers, loading flags, and last-executed search keys in session state when possible - let the page effect schedule the search, but put the duplicate-execution guard inside the search executor so page-load auto-search still runs after hydration - if the search text or filters are mirrored from localStorage on mount, expect that mount-time writes can re-trigger the effect unless the executor has its own guard See `PROJECT__Stores_Svelte5_Migration.md` for migration status and the pattern to follow when migrating remaining stores. ### `{#await}` blocks ```svelte {#await somePromise} {:then result}
{result}
{:catch error} {/await} ``` --- ## 5. V3 API Patterns ### SWR (Stale-While-Revalidate) — the standard load pattern Return cached Dexie data immediately, refresh from API in background. ```ts 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 }); } ``` ### Shared/Common Aether object fields The core fields for almost all Aether objects are: * id/id_random * code - string * name - string * summary - string * content - string * alert - boolean * alert_msg - text * priority - boolean * sort - int * group - string * hide - boolean * enable - boolean * default_qry_str - special concat string index * notes - text * created_on - timestamp * updated_on - timestamp ### 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. The integer version of the ID is never returned by the API. Always use the short form: ```ts // ✅ Correct event_file_obj.event_file_id // ❌ Wrong — legacy alias, don't use event_file_obj.event_file_id_random ``` The short ".id" is also the randomized string, **not an integer** (autonum). ### PATCH — only field values in the body ```ts // 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: x-account-id: ``` **Do not treat `params.key` as an auth bypass.** Only explicit `x-no-account-id: bypass` means "drop account context". If `key` is present for business logic, keep `x-account-id` intact. ### Dexie queries — always use the object ID index, not `.get()` All `db_core` (and other module) Dexie tables define their schema with `id` as the first field (primary key), followed by the object's string ID (e.g. `person_id`). V3 **never** returns `id`, so every record stored in Dexie has `id = undefined`. Calling `.get(value)` does a primary key lookup — it will always miss when passed a string object ID. ```ts // ❌ Wrong — .get() uses the primary key (id), which V3 never populates: liveQuery(() => db_core.person.get(person_id)) // ✅ Correct — use .where() on the indexed object ID field: liveQuery(() => db_core.person.where('person_id').equals(person_id).first()) ``` This applies to every table in every module (`db_core`, `db_events`, etc.). When looking up a single object by its string ID, always use `.where().equals().first()`. --- ## 6. Naming Conventions (snake_case; no camelCase) | Pattern | Example | Used for | |---|---|---| | `ae_comp__*` | `ae_comp__event_badge.svelte` | Route-level components | | `ae__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___.ts` | `ae_journals__journal.ts` | Object type + functions | | `db_.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. Common Mistakes (Reference) The full, curated mistake catalog now lives in `documentation/REFERENCE__Common_Agent_Mistakes.md`. Read this section first, then open the reference doc when your task touches one of these areas: 1. **Security/Auth** — private route guards, account scoping, and pre-gate data load risks. 2. **V3 API payloads** — object ID in URL, `data_kv` field-only PATCH payloads. 3. **Dexie/IDB behavior** — `.get()` primary key trap, stale cache/version mismatches, broad result clipping. 4. **Svelte 5 reactivity** — coarse-store `$effect` loops and `$`-sigil misuse on plain props. 5. **Sorting and search correctness** — `tmp_sort_*` comparator direction and Dexie sorting caveats. 6. **Network reliability** — retry classification in `api_*_object.ts` and timeout behavior. 7. **JSON field safety** — `*_json` null reads/writes and wrapper serialization behavior. 8. **Service worker rollout behavior** — stale-tab symptoms, activation expectations, and trade-offs. The reference doc also includes a short archive list for older, less-relevant historical incidents. --- ## 8. Source Layout (Quick Reference) ```text 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 | | Documentation index | `documentation/README__Docs_Index.md` | | Dev workflow + commit rules | `documentation/GUIDE__Development.md` | | V3 API reference | `documentation/GUIDE__AE_API_V3_for_Frontend.md` | | WebSockets / real-time updates | `documentation/GUIDE__AE_API_V3_for_Frontend_websockets.md` | | Dexie / liveQuery patterns | `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md` | | Common mistakes reference | `documentation/REFERENCE__Common_Agent_Mistakes.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` | | Journals module overview | `documentation/MODULE__AE_Journals.md` | | Journals settings map | `documentation/MODULE__AE_Journals_Config_Map.md` | | Exhibitor Leads module | `documentation/MODULE__AE_Events_Leads.md` | | Presentation Management module | `documentation/MODULE__AE_Events_Presentation_Management.md` | | IDAA client architecture | `documentation/CLIENT__IDAA_and_customized_mods.md` | | IDAA Archives module | `documentation/MODULE__AE_IDAA_Archives.md` | | IDAA Bulletin Board module | `documentation/MODULE__AE_IDAA_Bulletin_Board.md` | | IDAA Recovery Meetings module | `documentation/MODULE__AE_IDAA_Recovery_Meetings.md` | | IDAA Video Conferences module | `documentation/MODULE__AE_IDAA_Video_Conferences.md` | | Naming conventions | `documentation/AE__Naming_Conventions.md` |