docs: seventh-pass archive unsafe legacy references

This commit is contained in:
Scott Idem
2026-06-12 17:07:03 -04:00
parent e6fb4b289f
commit 75e7ca541a
12 changed files with 61 additions and 17 deletions

View File

@@ -0,0 +1,34 @@
# Documentation Archive Index
This directory preserves completed projects, superseded proposals, historical task logs, and legacy technical references.
Archived files are not authoritative for current implementation. Start with `documentation/README__Docs_Index.md` and the active module/guides directory.
## Categories
### Completed or Superseded Projects
Files prefixed with `PROJECT__` document completed implementation phases or superseded project plans.
### Historical Proposals
Files prefixed with `PROPOSAL__` preserve design ideas that are not the current implementation source.
### Task History
Files prefixed with `TODO__Agents__ARCHIVE_` contain completed task history by month.
### Legacy References
Files prefixed with `REFERENCE__Legacy_` are retained for historical context but contain pre-V3, pre-runes, or otherwise superseded guidance.
Do not copy implementation patterns from legacy references without validating them against current source and active guides.
## Restore Policy
Move an archived doc back to the active documentation root only when:
1. Its subject is active again.
2. Its content has been reviewed against current source.
3. Legacy paths, IDs, stores, and API conventions have been updated.
4. It is added to `documentation/README__Docs_Index.md`.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
# Aether Project Components
This document details the various UI components used throughout the Aether SvelteKit frontend project, categorized by their scope and functionality.
## 1. Aether Components (UI/UX)
### 1.1. System Components
These components are part of the core application shell and provide global functionalities.
- **`header`**: Application-wide header.
- **`main/module`s**: Main content area for modules.
- **`footer`**: Application-wide footer.
- **`app`**: Provides global application functionalities such as:
- Refresh application state.
- Clear IndexedDB.
- Clear local storage (settings).
- Toggle iframe visibility (also updates URL parameter).
- Copy current URL.
- Generate and display QR codes.
- **`menu`**: Various menus for different purposes:
- **`mode`**: Edit mode toggle, more options (all or details).
- **`access_type`**: Passcode input, clear access.
- **`user`**: Sign in/out, reset password, email link, change username and email.
- **`theme`**: Mode (light/dark), name (theme list).
- **`debug`**: Developer-facing tools:
- Toggle debug mode (also updates URL parameter).
- Show core and module storages.
- Manually set initial timestamp.
- **`scroll_to`**: Navigation controls for scrolling:
- Scroll to top of the page.
- Scroll page up.
- Scroll page down.
- Scroll to bottom of the page.
### 1.2. Core Components
These are reusable components that provide common functionalities across different modules.
- **`copy_btn`**: A button to copy content to the clipboard.
- Properties: `clipboard`, `bind:value`, `btn_text`, `btn_html`.
- **`txt_editor`**: A basic text area editor.
- **`md_editor`**: Markdown/rich-text editing handled by two active components:
- `element_editor_codemirror.svelte` — CodeMirror 6, used for source/code editing
- `element_editor_tiptap.svelte` — TipTap (WYSIWYG), used for rich-text content fields
- **`html_editor`**: HTML editor.
- **`media_player`**: Component for playing media files.
- Properties: `hosted_file`, `archive_content`, `media_player`.
- Bindings: `bind:host_id`, `bind:media_type`.
- Status: `stopped`, `paused`, `playing`.
- **`hosted_file_li`**: Manages a list of hosted files, making them available for selection.
- **`hosted_file_link_to`**: Lists links per object, with bindings to add/remove links.
- **`upload_to_host`**: Component for uploading files to the host.
- Handles multiple files.
- Properties: `link_type`, `link_id`, `inner fragment` (label html).
- Bindings: `bind:trigger`, `bind:show_spinner`, `bind:show_percent`.
- Status: `started`, `uploading`, `finished`.
- **`upload_file_tbl`**: Table for uploaded files, includes checks for duplicate file hashes and removal from the list.
- **`download_from_host`**: Component for downloading files from the host.
- Bindings: `bind:host_file_id`, `bind:filename`, `bind:file_ext`.
- Properties: `btn inner fragment`.
- Bindings: `bind:trigger`, `bind:show_spinner`, `bind:show_percent`.
- Status: `started`, `downloading`, `finished`.
- **`data_store`**: Component for interacting with data stores.
- **`element_ae_obj_field_editor`**: Standard single-field inline editor. Replaces retired `ae_crud` v1/v2 components.
- Props: `object_type`, `object_id`, `field_name`, `field_type`, `current_value`
- Field types: `text`, `textarea`, `select`, `tiptap`, `checkbox`, `date`, `datetime`, `number`
- Callbacks: `on_success`, `on_error`
- Respects `$ae_loc.edit_mode` — edit trigger hidden when edit mode is off.
- **`sql_qry`**: Component for executing SQL queries.
- **`obj_tbl`**: Object SQL results table or similar.
- **`qr_scanner`**: Component for scanning QR codes.
- **`websocket`**: Component for WebSocket communication.
### 1.3. Main / Module Components
These are components specific to main application sections or individual modules.
- **`menu`**:
- **`options`**: Various settings, show/hide content and options, limit, sorting options.
- **`actions`**: Various actions, sign in/out, email.
### 1.4. Object Menu
A standardized menu for interacting with objects.
- **Properties Displayed:** `id`, `name`, `group`, `priority`, `sort`, `alert`, `hide`, `enable`, `note`.
- **Future Properties:** `ext_id`, `ext_sys_id`, `code` (not yet ready).
- **Actions:** `create`, `view`, `edit`, `update`, `hide`, `disable`, `delete`, `alert` (message), `archive` (not yet ready).
- **Future Actions:** `copy`, `import`.
- **Sort Options:**
- `[default]`: `group > priority (flag) > sort (ASC/DESC) > alert > name`
- `[sort_updated]`: `group > priority (flag) > sort (ASC/DESC) > alert > updated_on > created_on`
- `[priority_updated]`: `group > priority (flag) > updated_on (ASC/DESC) > created_on`
- `[priority_name]`: `group > priority (flag) > name (ASC/DESC) > sort > alert > updated_on > created_on`
- `[name]`: `priority (flag) > name (ASC/DESC) > sort > alert > updated_on > created_on`
- `[created_on]`: `priority (flag) > created_on (ASC/DESC)`
- `[updated_on]`: `priority (flag) > updated_on (ASC/DESC) > created_on`
## 2. Pop-ups
Standardized structure for various types of pop-up elements.
- **`modal_header`**:
- `title`
- `close` button
- **`modal_main`**: Main content area of the modal.
- **`modal_meta`**: Meta-information section.
- **`modal_footer`**:
- `close` button
- **`Pop-up Modal (blocking)`**: A modal that blocks interaction with the rest of the page.
- `modal position`
- **`Pop-up Modal Inline`**: A modal that appears inline with content.
- `inline`, `inline-block`, `block` display options.
- **`Pop-up Dialog`**: A dialog box.
- `dialog position`
## 3. Containers
Generic container types used for layout and grouping.
### 3.1. Navigation
- `link`
- `download`
### 3.2. Forms
- `save` button/action
- `clear value` action
- `set null value` action
### 3.3. Other Containers
- `help`: Blue themed container.
- `info`: Blue themed container.
- `alert`: Yellow themed container.
- `warning`: Orange themed container.
- `error`: Red themed container.
- `message`: Green themed container.
## 4. CSS Styling for UI Elements
- **Warning/Hide Buttons:** `preset-tonal-warning hover:preset-filled-warning-500`
- **Error/Delete/Disable Buttons:** `preset-tonal-error hover:preset-filled-error-500`
- **Submenu:** `flex flex-row items-center justify-center gap-1`

View File

@@ -0,0 +1,100 @@
# Aether Project Data Structures
This document outlines the key data structures and their properties used within the Aether SvelteKit frontend project. It covers object properties, field definitions, and how data is managed.
## 1. Object Properties and Fields
### 1.1. Core Standard Fields
These fields are expected to be present in most Aether objects, providing a consistent base structure.
- `id`: Primary key for an object (internal use, often *returned* by the API as a randomized string value in place of the actual DB autonum).
- `id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
- `<object_type>_id_random`: Specific random ID for an object (e.g., `person_id_random`).
- `code`: Short, unique identifier.
- `name`: Display name.
- `enable`: Boolean for active/inactive status.
- `hide`: Boolean for visibility.
- `priority`: Boolean/tinyint(1) ordering flag used by the object model.
- `sort`: Numeric value for ordering within a priority group.
- `group`: Categorization string.
- `notes`: General notes/comments.
- `created_on`: Timestamp of creation.
- `updated_on`: Timestamp of last update.
### 1.2. Journal Entry Fields
Journal entries use the shared object fields plus a few content-specific fields that matter in the UI and config modal.
- `summary`: Short entry summary shown in metadata and list contexts.
- `content`: Main body text for the entry.
- `alert`: Boolean flag used to highlight an entry as an alert.
- `alert_msg`: Supporting alert text shown when the alert flag is enabled.
- `private` / `public` / `personal` / `professional`: Visibility and audience flags used by the Entry Config modal.
### 1.3. Special Use Fields
Fields with specific purposes or conditional usage across different object types.
- `for_type`: Indicates the type of object this object is linked to (e.g., 'account', 'event').
- `for_id`: The ID of the object this object is linked to.
- `archive_on`: Timestamp indicating when an object was archived.
- `passcode`: A password or access code associated with an object.
- `external_id`: An identifier from an external system.
### 1.4. Configuration and JSON Fields
Fields designed to store structured data in JSON format.
- `cfg_json`: Configuration data for an object, stored as a JSON string.
- `data_json`: General purpose data for an object, stored as a JSON string.
- `linked_li_json`: A list of linked items, stored as a JSON string.
### 1.5. Special Generated Fields (Client-side)
These fields are generated on the client-side, primarily for facilitating UI logic, such as sorting. They are not typically stored in the backend database.
- `tmp_sort_1`: Temporary sort field 1.
- `tmp_sort_2`: Temporary sort field 2.
- `tmp_sort_3`: Temporary sort field 3.
### 1.6. Future Standard Fields
A list of potential future standard fields, often prefixed with `obj_`. These are conceptual and represent planned expansions to the data model.
- `obj_id`, `obj_ext_uid`, `obj_ext_id`, `obj_import_id`, `obj_code`, `obj_account_id`, `obj_passcode`, `obj_type`, `obj_type_ver_id`, `obj_name`, `obj_summary`, `obj_outline`, `obj_description`, `obj_enable`, `obj_enable_on`, `obj_archive_on`, `obj_hide`, `obj_priority`, `obj_sort`, `obj_group`, `obj_cfg_json`, `obj_notes`, `obj_created_on`, `obj_updated_on`.
## 2. Data Sorting
Standardized sorting orders are applied across various data lists to ensure consistent presentation.
- **Default/General Sorting:** `group > priority (flag) > sort > updated_on/created_on`
- **Specific Sorting (e.g., for time-based events):** `type > start_date/time > code or name`
## 3. Data Storage Mechanisms
### 3.1. Local Storage
Used for client-side persistence of various application states and configurations.
- `api`: Stores API-related settings and tokens.
- `app`: Stores global application settings and preferences.
- `core`: Stores settings and data specific to core modules.
- `<module>`: Stores settings and data specific to extended modules (e.g., `journals`, `events`).
- `<custom>`: Stores settings and data specific to custom modules (e.g., `idaa`).
### 3.2. IndexedDB (Dexie.js)
Used for more structured client-side data storage, often for caching, offline capabilities, and larger datasets.
- `ae_core_db`: The primary Dexie database instance for core application data.
- `<module>`: Module-specific database instances (e.g., `db_journals` for journal data).
- `<custom>`: Custom module-specific database instances (none currently defined, but reserved for future use).
### 3.3. IndexedDB LiveQuery Usage
Dexie's `liveQuery` is used to provide reactive data streams from IndexedDB.
- `lq__xyz_obj`: Represents a read-only liveQuery result for a single object.
- `lqw__xyz_obj`: Represents a writable liveQuery result, typically used for forms and data binding.
- **Note:** When using `lqw__xyz_obj`, developers must carefully manage updates to avoid conflicts with the underlying liveQuery and ensure data integrity.

View File

@@ -0,0 +1,240 @@
# Performance Guidelines: Non-Blocking Load Pattern (SvelteKit + Dexie)
## Overview
To ensure instant page transitions and a high-performance feel, the Aether platform utilizes a **Non-Blocking Load Pattern** (also known as Stale-While-Revalidate or SWR). This pattern leverages Dexie's `liveQuery` for reactive UI and SvelteKit's `load` functions for background data synchronization.
## 🚀 The Core Principle
**Never block the `load` function with API calls if the data is already being observed by a `liveQuery`.**
The page should render *instantly* using cached data from IndexedDB. Fresh data from the API should settle in the background and update the UI automatically via reactivity.
---
## ❌ Anti-Pattern (Blocking)
This pattern causes a "white screen" or "frozen UI" while the browser waits for the API response.
```typescript
// +page.ts
export async function load({ params, parent }) {
const data = await parent();
const event_id = params.event_id;
// BAD: This blocks the navigation until the API responds.
const fresh_data = await events_func.load_ae_obj_id__event({
event_id: event_id,
try_cache: true
});
return { ...data, event_obj: fresh_data };
}
```
## ✅ Best Practice (Non-Blocking / SWR)
This pattern completes the navigation immediately.
```typescript
// +page.ts
export async function load({ params, parent }) {
const data = await parent();
const event_id = params.event_id;
if (browser) {
// GOOD: Fire and forget.
// This function updates IndexedDB in the background.
events_func.load_ae_obj_id__event({
event_id: event_id,
try_cache: true
});
}
return data; // Navigation completes instantly
}
```
```svelte
<!-- +page.svelte -->
<script lang="ts">
import { liveQuery } from 'dexie';
import { db_events } from '$lib/ae_events/db_events';
// UI reacts automatically when the background task finishes.
let lq__event_obj = $derived(
liveQuery(() => db_events.event.get(event_id))
);
</script>
{#if $lq__event_obj}
<h1>{$lq__event_obj.name}</h1>
{:else}
<p>Loading...</p>
{/if}
```
---
## 🛠️ When to use Await
Use `await` in `load` functions ONLY for:
1. **Critical Auth Checks:** If you must verify a session before even showing a layout.
2. **Parent Data:** `const data = await parent();` is necessary to build the context.
3. **Server-Side Rendering (SSR):** If the data *must* be present in the initial HTML for SEO (rare for Aether feature modules).
## 📈 Performance Gains
By adopting this pattern across the Events module, we achieved:
- **~200-500ms reduction** in perceived page load time.
- **Elimination of waterfalls** (sequential API calls).
- **Better offline support**, as the UI is always ready to show what's in the local cache.
---
## Svelte 5 Runes + liveQuery: Critical Patterns
These rules apply to all Svelte 5 runes-mode components (the entire Aether frontend). Violations here are a common source of subtle reactivity bugs and unnecessary re-renders.
### Rule 1: Use `$derived.by()` when liveQuery depends on a reactive value
**The problem:** `$derived(liveQuery(callback))` looks like it should re-run when a store value inside `callback` changes. It does NOT. Svelte tracks reactive dependencies synchronously during the expression evaluation. The `liveQuery` callback is called later inside Dexie's async context — Svelte's tracking is already finished. The dependency is never registered.
```svelte
<!-- ❌ WRONG: $events_slct.event_session_id is read inside the async callback.
Svelte never tracks it. The liveQuery is created once and never recreates
when event_session_id changes. -->
let lq__session = $derived(
liveQuery(() => db_events.session.get($events_slct.event_session_id))
);
```
```svelte
<!-- ✅ CORRECT: $derived.by() captures the ID in the outer synchronous closure.
Svelte tracks it. When event_session_id changes, $derived.by() re-runs,
creating a new liveQuery with the updated ID. -->
let lq__session = $derived.by(() => {
const id = $events_slct.event_session_id; // tracked here, synchronously
return liveQuery(() => db_events.session.get(id));
});
```
**Rule of thumb:** If the liveQuery result changes based on a reactive value (store property, `$state`, `$props`), always use `$derived.by()`. Reserve `$derived(liveQuery(...))` only for liveQueries that watch a table broadly and don't filter by a reactive value.
---
### Rule 2: Keep liveQuery closures pure (data-only)
**The problem:** Writing to a Svelte store inside a liveQuery callback runs inside Dexie's async transaction context. Svelte's reactive tracking is undefined there. The write may fire at unpredictable times and create hard-to-debug reactivity loops.
```svelte
<!-- ❌ WRONG: Store side-effect inside liveQuery async callback. -->
let lq__event_obj = liveQuery(async () => {
const obj = await db_events.event.get($events_slct.event_id);
if (obj) $events_slct.event_obj = obj; // BAD: side-effect in async context
return obj;
});
```
```svelte
<!-- ✅ CORRECT: liveQuery is pure data-only. Store sync happens in a $effect. -->
let lq__event_obj = liveQuery(async () => {
const id = $events_slct.event_id;
if (!id) return null;
return await db_events.event.get(id);
});
$effect(() => {
const result = $lq__event_obj;
if (result) {
untrack(() => {
// Cheap equality guard — only write if something actually changed.
if (result.updated_on !== $events_slct.event_obj?.updated_on ||
result.id !== $events_slct.event_obj?.id) {
$events_slct.event_obj = { ...result };
}
});
}
});
```
---
### Rule 3: Use cheap equality guards in `$effect` before writing to stores
Every store write in a `$effect` triggers downstream reactivity. Always guard with a comparison before writing. The cost of the comparison is always less than the cost of spurious re-renders.
**For single objects** — compare `id` + `updated_on` (O(1)):
```typescript
if (result.id !== $store.obj?.id || result.updated_on !== $store.obj?.updated_on) {
$store.obj = { ...result };
}
```
**For arrays** — join IDs into a string (O(n)), not `JSON.stringify` (O(n × field_count)):
```typescript
const new_ids = results.map(r => r.id).join(',');
const cur_ids = ($store.list ?? []).map(r => r.id).join(',');
if (new_ids !== cur_ids) {
$store.list = [...results];
}
```
**For flat objects** (e.g., merged config) — shallow key-by-key comparison (O(n keys)):
```typescript
function shallow_equal(a, b) {
const keys_a = Object.keys(a);
const keys_b = Object.keys(b);
if (keys_a.length !== keys_b.length) return false;
for (const k of keys_a) { if (a[k] !== b[k]) return false; }
return true;
}
if (!shallow_equal(current, new_val)) { $store = new_val; }
```
**Never use `JSON.stringify` for equality.** It serializes the full object tree on every reactive cycle and is O(total serialized bytes).
---
### Rule 4: Always use `untrack()` when writing to stores inside `$effect`
Without `untrack()`, reading a store to check its current value inside `$effect` registers it as a dependency — the effect re-runs whenever it writes, creating an infinite loop.
```svelte
<!-- ❌ WRONG: Reading $store.obj inside $effect creates a dependency loop. -->
$effect(() => {
const result = $lq__obj;
if (result.id !== $store.obj?.id) { // Reading $store.obj here is a dependency!
$store.obj = result; // This write re-triggers the effect.
}
});
```
```svelte
<!-- ✅ CORRECT: untrack() reads current store values without registering them
as reactive dependencies of the $effect. -->
$effect(() => {
const result = $lq__obj; // Tracked: effect re-runs when liveQuery emits
if (result) {
untrack(() => {
// Not tracked: reading $store.obj here won't cause a re-run.
if (result.id !== $store.obj?.id) {
$store.obj = result;
}
});
}
});
```
---
### Rule 5: Guard `console.log` calls with `log_lvl`
Raw `console.log(obj)` eagerly serializes objects (even large ones) on every call, blocking the main thread. All debug logging must be guarded.
```typescript
let log_lvl: number = $state(0); // Set to 0 in production; raise locally to debug.
// ❌ WRONG: Always runs, always serializes.
console.log('Result:', result_obj);
// ✅ CORRECT: Zero-cost when log_lvl is 0.
if (log_lvl) console.log('Result:', result_obj);
if (log_lvl > 1) console.log('Verbose:', result_obj); // Extra-verbose tier
```
**Never hardcode `log_lvl: 2` in a call-site or override `log_lvl` inside a function body.** The parameter default exists so callers can control verbosity. Overriding it forces debug logging regardless of what the caller passed.

View File

@@ -0,0 +1,414 @@
# Aether UI — Component Style Patterns
> **Version:** 1.0 (2026-03-06)
> **Author:** One Sky IT / Scott Idem
> **Scope:** All Aether SvelteKit frontend components
> **Related:** `GUIDE__AE_UI_Style_Guidelines.md` (color rules, token definitions, a11y)
This document is a recipe book. Copy these patterns directly. Deviate only when a component's specific purpose genuinely requires it — and document why in a comment.
---
## 1. Hero Card
*Used for: session identity, presenter identity, location identity — the top-of-page "Is this the right one?" card.*
```svelte
<div class="rounded-xl border border-surface-200-800 bg-surface-50-900 shadow-sm overflow-hidden">
<div class="px-4 pt-4 pb-3 flex flex-col gap-3">
<!-- primary heading (h1 / h2) -->
<h1 class="text-2xl font-bold leading-snug">{name}</h1>
<!-- info chips row -->
<div class="flex flex-wrap gap-2 items-center">
<!-- time chip → primary color -->
<span class="inline-flex items-center gap-1.5 text-sm font-semibold px-3 py-1 rounded-full bg-primary-500/10 text-primary-700 dark:text-primary-300 transition-colors duration-200">
<span class="fas fa-clock text-xs" aria-hidden="true"></span>
Mon, Jan 1 2:00 PM
</span>
<!-- room/location chip → tertiary color -->
<span class="inline-flex items-center gap-1.5 text-sm font-semibold px-3 py-1 rounded-full bg-tertiary-500/10 text-tertiary-700 dark:text-tertiary-300 transition-colors duration-200">
<span class="fas fa-map-marker-alt text-xs" aria-hidden="true"></span>
Room 201
</span>
</div>
</div>
</div>
```
**Skeleton loading variant:**
```svelte
<!-- While liveQuery resolves -->
<div class="h-7 w-2/3 bg-surface-200-800 animate-pulse rounded"></div>
<div class="h-5 w-1/2 bg-surface-200-800 animate-pulse rounded-full"></div>
```
---
## 2. Standard Content Card
*Used for: description text, notes, secondary info panels.*
```svelte
<div class="rounded-lg border border-surface-200-800 bg-surface-50-900 px-4 py-3">
<!-- optional eyebrow label -->
<span class="text-xs font-bold uppercase tracking-wide opacity-40 block mb-1">Description</span>
<p class="whitespace-pre-wrap text-sm leading-relaxed">{description}</p>
</div>
```
**Variant — inner secondary panel:**
```svelte
<div class="bg-surface-100-900 rounded-lg px-3 py-2">
<!-- inner content -->
</div>
```
---
## 3. Table Row
*Used for: session search results tables, any `<tbody><tr>` list.*
```svelte
<tr
class="relative transition-colors duration-200"
class:opacity-50={obj?.hide}
class:preset-tonal-warning={!obj?.enable}
>
<td>
<a
href="/path/to/{obj.id}"
class="font-bold text-lg hover:text-primary-500 transition-colors duration-200"
>
{obj.name}
</a>
</td>
</tr>
```
- `opacity-50` for hidden/archived records
- `preset-tonal-warning` for disabled (not enabled) records — amber background
- `transition-colors duration-200` on both `<tr>` and `<a>`
---
## 4. List Item Card
*Used for: presentation list items, session details lists, any vertical card stack.*
```svelte
<ul class="space-y-4">
<li class="space-y-3 border border-surface-200-800 bg-surface-50-900 p-4 rounded-xl shadow-sm transition-colors duration-200">
<!-- Card heading bar -->
<h4 class="text-lg font-bold rounded-lg px-3 py-2 bg-surface-100-900 flex flex-wrap items-center gap-2">
{name}
<!-- code/tag badge -->
<span class="text-xs preset-tonal-warning px-2 py-0.5 rounded-md leading-none">
{code}
</span>
</h4>
<!-- Description block -->
<pre class="whitespace-pre-wrap p-3 bg-surface-100-900 rounded-lg text-sm">{description}</pre>
</li>
</ul>
```
**Rules:**
- Background goes on `<li>`, NOT on `<ul>`
- `<ul>` gets only spacing: `space-y-4` — never a background color
- The `<ul>` container in components should have `overflow-x-auto`, not `overflow-x-scroll`
---
## 5. Info Chips
### Time / Date chip (Primary — Teal)
```svelte
<span class="inline-flex items-center gap-1.5 text-sm font-semibold px-3 py-1 rounded-full bg-primary-500/10 text-primary-700 dark:text-primary-300 transition-colors duration-200">
<span class="fas fa-clock text-xs" aria-hidden="true"></span>
Monday, March 6 2:00 PM
</span>
```
### Location / Room chip (Tertiary — Indigo)
```svelte
<span class="inline-flex items-center gap-1.5 text-sm font-semibold px-3 py-1 rounded-full bg-tertiary-500/10 text-tertiary-700 dark:text-tertiary-300 transition-colors duration-200">
<span class="fas fa-map-marker-alt text-xs" aria-hidden="true"></span>
Main Hall B
</span>
```
### Code / Tag badge
```svelte
<span class="text-xs preset-tonal-warning px-2 py-0.5 rounded-md leading-none">
{code}
</span>
```
### Status badge (edit mode only)
```svelte
{#if $ae_loc.edit_mode}
<span class="badge preset-tonal-surface text-xs">code: {obj.code}</span>
{/if}
```
### Success count badge
```svelte
<span class="badge preset-tonal-success" class:hidden={!fileCount}>
<span class="fas fa-file-alt m-1" aria-hidden="true"></span>
{fileCount}×
</span>
```
---
## 6. Empty State Panel
*Used for: "No results found", "No sessions match your search", "Nothing to show yet".*
```svelte
<section
class="preset-tonal-warning p-6 rounded-xl shadow-sm lg:max-w-lg mx-auto"
role="status"
aria-live="polite"
>
<div class="flex flex-col items-center gap-2 text-center">
<span class="fas fa-search text-3xl opacity-50" aria-hidden="true"></span>
<strong class="text-xl">No sessions found</strong>
<p class="text-base opacity-80">
Use the search bar above to find your session.
</p>
</div>
<!-- optional details card -->
<div class="bg-surface-50-900/60 rounded-lg p-3 mt-4">
<span class="text-xs font-bold uppercase tracking-wide opacity-50 block mb-2">Search by any of:</span>
<ul class="space-y-1 text-sm">
<li class="flex items-center gap-1.5">
<span class="fas fa-angle-right text-xs opacity-50" aria-hidden="true"></span>
Session name
</li>
</ul>
</div>
</section>
```
---
## 7. Warning / Error Inline Banners
*Used for: disabled records, agreement-required gates.*
```svelte
<!-- Warning (amber — disabled/inactive) -->
<div class="bg-warning-100 p-4 border border-warning-300 rounded-md">
<h2 class="h3">
<span class="fas fa-exclamation-triangle text-warning-500 m-1" aria-hidden="true"></span>
Location Disabled
</h2>
<p>This location is currently disabled.</p>
</div>
<!-- Error (red — blocked/failed) -->
<div class="bg-error-100 p-4 border border-error-300 rounded-md">
<h2 class="h3">
<span class="fas fa-exclamation-triangle text-error-500 m-1" aria-hidden="true"></span>
Presenter Disabled
</h2>
<p>This presenter is currently disabled.</p>
</div>
```
---
## 8. File Upload Zone (`Comp_event_files_upload`)
The `class_li` prop styles the outer upload drop zone container:
```svelte
<Comp_event_files_upload
class_li="border border-surface-200-800 rounded-xl p-4 bg-surface-50-900 hover:bg-surface-100-900 transition-colors duration-200"
link_to_type="event_presenter"
link_to_id={presenter_id}
>
{#snippet label()}
<span>
<div class="text-lg">
<span class="fas fa-upload" aria-hidden="true"></span>
<strong>Upload presenter files</strong>
</div>
<div class="text-sm opacity-60 italic">
Supported: pptx, key, mp4, pdf, docx, xlsx, txt
</div>
</span>
{/snippet}
</Comp_event_files_upload>
```
**Note:** The label sub-description text uses `opacity-60 italic` — never `text-gray-600 dark:text-gray-400`.
---
## 9. Section Component Wrapper
*Used for: `ae_comp__event_*_obj_li.svelte` outer `<section>` elements.*
```svelte
<section
class="ae_comp event_X_obj_li px-0.5 py-2 space-y-2 min-w-full w-full container overflow-x-auto {container_class_li}"
>
```
**Rules:**
- `overflow-x-auto` — never `overflow-x-scroll`
- **Never include debug breakpoint borders** — remove before committing:
```
sm:border-l-red-400 md:border-l-yellow-400 lg:border-l-gray-100
sm:dark:border-l-red-600 md:dark:border-l-yellow-600 lg:dark:border-l-gray-700
border-dashed border-y-transparent border-r-transparent
```
---
## 10. Agreement / Consent Form Layout
*Used for: `ae_comp__event_presenter_form_agree.svelte`, `ae_comp__event_session_poc_form_agree.svelte`.*
```svelte
<!-- Consent text container -->
<div class="bg-surface-100-900 p-4 border border-surface-200-800 rounded-lg space-y-4">
<Element_data_store ds_code="consent_text" ds_type="html" class_li="p-2" />
</div>
<!-- Presenter name/identity highlight line -->
<p class="text-lg preset-tonal-warning p-2 rounded-t-md">
<strong>{presenter_name} ({email})</strong>
agrees to the following terms and conditions:
</p>
```
---
## 11. Modal Usage (Flowbite-Svelte)
```svelte
<!-- ✅ Correct — no manual color class; theme handles styling -->
<Modal title="Host Profile" bind:open={show_modal}>
<ProfileComponent />
{#snippet footer()}
<button onclick={() => show_modal = false} class="btn preset-tonal-warning">
Close
</button>
{/snippet}
</Modal>
<!-- ❌ Wrong — manual gray overrides bypass the theme -->
<Modal class="bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 ...">
```
**Rule:** Never set `bg-*` or `text-*` color classes on `<Modal>`. Let the Flowbite component + active theme handle it. Only structural layout classes (`shadow-md`, `relative`, `flex`, etc.) belong on the `class` prop if needed.
---
## 12. Muted / Secondary Text
Replace all `text-gray-*` patterns with opacity wrappers on inherited text color:
```svelte
<!-- ✅ Theme-aware muted text -->
<span class="text-sm opacity-60">Secondary label</span>
<span class="text-sm opacity-40 italic">Hint or placeholder text</span>
<span class="text-xs font-bold uppercase tracking-wide opacity-40">Section eyebrow</span>
<!-- ❌ Fixed-color muted text -->
<span class="text-sm text-gray-500">Secondary label</span>
<span class="text-sm text-gray-600 dark:text-gray-400 italic">Hint text</span>
```
---
## 13. QR Code (Async Toggle)
```svelte
<!-- Gate on typeof === 'string', not truthy.
The store holds boolean `true` as a loading placeholder, which would
render as a broken <img src="true"> if not guarded. -->
{#if $lq__obj && typeof $store.qr_url?.[$lq__obj.id] === 'string'}
<div class="float-right ml-3 mb-1 flex flex-col items-center gap-1">
<button
type="button"
onclick={() => $store.qr_bigger = !$store.qr_bigger}
class="rounded focus-visible:ring-2 focus-visible:ring-primary-500"
title="Toggle QR code size"
aria-label="Toggle QR code size"
>
<img
src={$store.qr_url[$lq__obj.id]}
class="transition-all duration-500 rounded border border-surface-200-800"
class:h-20={!$store.qr_bigger}
class:w-20={!$store.qr_bigger}
class:h-40={$store.qr_bigger}
class:w-40={$store.qr_bigger}
alt="QR code link to this page"
/>
</button>
</div>
{/if}
```
---
## 14. POC / Host Button Pattern
```svelte
<div class="flex items-center gap-2">
<span class="text-sm font-semibold opacity-60">Host:</span>
<button
type="button"
class="btn btn-sm preset-tonal-primary transition-colors duration-200"
onclick={() => show_profile = true}
aria-haspopup="dialog"
>
<span class="fas fa-id-card mr-1" aria-hidden="true"></span>
{full_name}
</button>
</div>
```
---
## 15. Icon Usage Rules
| Context | Pattern |
|---|---|
| Decorative / visual only | `<span class="fas fa-clock" aria-hidden="true"></span>` |
| Icon with visible adjacent text | `aria-hidden="true"` on icon, text provides meaning |
| Icon-only button (no visible text) | `aria-label="Description"` on the `<button>` |
| Icon used as bullet point | `aria-hidden="true"` on icon |
**Never use `<i>` tags.** Always `<span class="fas ...">`.
---
## 16. Native `<select>` Dark Mode
Browser-native `<select>` and `<option>` elements **cannot be reliably styled** with Tailwind `dark:` utilities — the browser controls `<option>` rendering and ignores most CSS overrides. This causes the "light on light hover" bug in dark mode.
**Fix — add `color-scheme` directive to force OS-level dark styling:**
```svelte
<script>
import { ae_loc } from '$lib/ae_core/ae_stores';
</script>
<!-- Forces browser to render the select widget in dark/light OS mode matching your theme -->
<select
class="select text-xs p-1"
style:color-scheme={$ae_loc.dark_mode ? 'dark' : 'light'}
>
{#each options as opt}
<option value={opt.value}>{opt.label}</option>
{/each}
</select>
```
**Why this works:** `color-scheme: dark` instructs the browser to use its native dark-mode widget rendering (dark `<select>`, dark `<option>` backgrounds). It's the only cross-browser mechanism that affects `<option>` hover colors.
**Alternative — replace with custom Skeleton/Flowbite component** if you need full styling control (e.g., color-coded options, icons). Native `<select>` is acceptable for simple purpose dropdowns with the `color-scheme` fix above.
**Store reference:** `$ae_loc.dark_mode` — boolean, set by the theme engine in `ae_stores.ts`.