Files
OSIT-AE-App-Svelte/documentation/BOOTSTRAP__AI_Agent_Quickstart.md
Scott Idem 81874ffa5d fix(sw): complete cache-clearing + add controllerchange auto-reload
All cache-clearing buttons and the IDAA clear-caches page previously
cleared IDB/localStorage but left service worker registrations and Cache
Storage intact. On the next reload the SW re-served the old JS bundle,
leaving users stuck on stale code despite appearing to reload. This
caused recurring stale-state reports from IDAA and other clients for
4+ months.

Two gaps closed:
1. Every clear path (root page buttons, sys bar, help tech, idaa/clear-caches)
   now unregisters SW registrations and clears Cache Storage before touching
   IDB and localStorage. Order: SW → Cache Storage → IDB → localStorage.
2. Added controllerchange listener in +layout.svelte effect 4. When the new
   SW activates and calls clients.claim(), this listener reloads the page so
   open tabs run the new JS bundle instead of keeping old code in memory
   indefinitely. Without this, skipWaiting + clients.claim work correctly on
   the SW side but the page side never picks up the update.

Also added thorough code comments and updated REFERENCE__Common_Agent_Mistakes
(#15) and BOOTSTRAP doc (#8) to document the full root cause so this cannot
be silently re-broken by a future agent or refactor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 12:17:51 -04:00

403 lines
17 KiB
Markdown

# Aether SvelteKit — AI Agent Bootstrap / Quickstart
> **Doc Owner:** Frontend platform maintainers (OSIT) + active coding agents
> **Review Trigger:** Update when module architecture, critical rules, or primary doc paths change.
> **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=<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
```svelte
<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)
```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}
<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.
```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: <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.
```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_<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 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 + cache clearing — MANDATORY four-layer wipe** — clearing IDB/localStorage
alone is a placebo. The SW Cache Storage is separate and must be cleared first or the SW
re-serves the old JS bundle on the very next reload. The `controllerchange` event listener in
`+layout.svelte` (effect 4) is also required or open tabs stay on old code after deploys.
See mistake #15 in the reference doc — this caused a recurring client issue for 4+ months.
9. **Local/remote config sync** — shadow fields that silently bypass an admin-synced master
field, SWR-await-after-write races, and stateful/conditional sync gates that desync local
state from history rather than current config. See the Pres Mgmt Config sync overhaul
(2026-06-16) in `PROJECT__AE_Events_PressMgmt_Config_Cleanup.md` for the full incident.
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. Agent Messaging (ae_send_message / ae_inbox)
Use MCP tools to coordinate with other agents — do not write directly to the inbox directories.
```ts
// Send a message to the backend agent
ae_send_message({ recipient: 'backend', sender: 'frontend', content: '...' })
// Check your inbox (reads from frontend_svelte/ by default in this project)
ae_inbox({ command: 'list', status: 'unread' })
```
### Valid `ae_send_message` recipient names
| Recipient name | Inbox directory | Agent |
| --- | --- | --- |
| `frontend` | `~/agents_sync/inbox/frontend_svelte/` | SvelteKit frontend agent (this agent) |
| `backend` | `~/agents_sync/inbox/backend_fastapi/` | FastAPI backend agent |
| `mcp_agent` | `~/agents_sync/inbox/mcp_agent/` | MCP/orchestration agent |
| `scott_wks` | `~/agents_sync/inbox/scott_wks/` | Scott's workstation |
| `scott_lpt` | `~/agents_sync/inbox/scott_lpt/` | Scott's laptop |
| `gemini_cli` | `~/agents_sync/inbox/gemini_cli/` | Gemini CLI agent |
> **Gotcha:** The inbox directory names (`frontend_svelte`, `backend_fastapi`) do NOT match the
> `ae_send_message` recipient names (`frontend`, `backend`). Always use the recipient names above —
> using the directory name will produce an `invalid choice` error.
### Checking your own inbox
The `ae_inbox` MCP tool reads from the `frontend_svelte` inbox by default when invoked in this project directory. Use `status='unread'` to see new messages, `status='all'` to see everything. New messages can also arrive as files in `~/agents_sync/inbox/frontend_svelte/` — check there if `ae_inbox` shows nothing but the user says a message was sent.
---
## 10. 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/MODULE__AE_Events_Launcher_Native.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` |