docs: seventh-pass archive unsafe legacy references
This commit is contained in:
34
documentation/archive/README.md
Normal file
34
documentation/archive/README.md
Normal 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
146
documentation/archive/REFERENCE__Legacy_Aether_Components.md
Normal file
146
documentation/archive/REFERENCE__Legacy_Aether_Components.md
Normal 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`
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
414
documentation/archive/REFERENCE__Legacy_UI_Component_Patterns.md
Normal file
414
documentation/archive/REFERENCE__Legacy_UI_Component_Patterns.md
Normal 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`.
|
||||
Reference in New Issue
Block a user