Files
OSIT-AE-App-Svelte/documentation/BOOTSTRAP__AI_Agent_Quickstart.md

14 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 only: x-aether-api-key + x-account-idNOT 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.

// 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 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

{#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 });
}

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:

// ✅ 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

// 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>

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.

// ❌ 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_<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. 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 correctnesstmp_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)

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/PROJECT__AE_Events_PressMgmt_Config_Cleanup.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